From 8fbaba2787de9b348abfda5ea81f0e63691530b9 Mon Sep 17 00:00:00 2001 From: Tim Urista Date: Wed, 15 Jul 2020 06:25:52 -0700 Subject: [PATCH 01/78] add myself to early adopters --- ADOPTERS.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 06eff65c12..1cef2ebe24 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,5 +1,6 @@ -| Organization | Contact | Description of Use | -| ------------ | ------- | ------------------ | -| [Spotify](https://www.spotify.com) |[@alund](https://github.com/alund)| Main interface towards all of Spotify's infrastructure and technical documentation.| -| [bol.com](https://www.bol.com) |[@RoyJacobs](https://github.com/RoyJacobs)| Initial work being done to unify platform tooling.| -| [DFDS](https://www.dfds.com) |[@carlsendk](https://github.com/carlsendk)| V2 self-service platform.| \ No newline at end of file +| Organization | Contact | Description of Use | +| ---------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- | +| [Spotify](https://www.spotify.com) | [@alund](https://github.com/alund) | Main interface towards all of Spotify's infrastructure and technical documentation. | +| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | +| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | +| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform | From db1a9230aeca5f744403f1cc2932e2e7cabec36f Mon Sep 17 00:00:00 2001 From: Tim Urista Date: Wed, 15 Jul 2020 06:26:31 -0700 Subject: [PATCH 02/78] fix formatting --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 1cef2ebe24..1ed3532262 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -3,4 +3,4 @@ | [Spotify](https://www.spotify.com) | [@alund](https://github.com/alund) | Main interface towards all of Spotify's infrastructure and technical documentation. | | [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | | [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform | +| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | From c1c14149bb1dba2ec5e178aaf3c49a5477c2af73 Mon Sep 17 00:00:00 2001 From: Tim Urista Date: Wed, 15 Jul 2020 06:29:54 -0700 Subject: [PATCH 03/78] fix formatting by my ide --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 1ed3532262..1b95c6e833 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -3,4 +3,4 @@ | [Spotify](https://www.spotify.com) | [@alund](https://github.com/alund) | Main interface towards all of Spotify's infrastructure and technical documentation. | | [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | | [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | +| [DFDS](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | From 933735a7244d0525abdeffbb2b26574c33385363 Mon Sep 17 00:00:00 2001 From: Tim Urista Date: Wed, 15 Jul 2020 06:30:59 -0700 Subject: [PATCH 04/78] forcing a commit to prevent weird formatting --- ADOPTERS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 1b95c6e833..85415a2657 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,6 +1,6 @@ -| Organization | Contact | Description of Use | -| ---------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- | -| [Spotify](https://www.spotify.com) | [@alund](https://github.com/alund) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | -| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [DFDS](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | +| Organization | Contact | Description of Use | +| ------------ | ------- | ------------------ | +| [Spotify](https://www.spotify.com) |[@alund](https://github.com/alund)| Main interface towards all of Spotify's infrastructure and technical documentation.| +| [bol.com](https://www.bol.com) |[@RoyJacobs](https://github.com/RoyJacobs)| Initial work being done to unify platform tooling.| +| [DFDS](https://www.dfds.com) |[@carlsendk](https://github.com/carlsendk)| V2 self-service platform.| +| [DFDS](https://www.roku.com) |[@timurista](https://github.com/timurista)| Initial work on Cloud engineering service platform.| From b44de296c183ac59aae42db0d646a5f1aaab5442 Mon Sep 17 00:00:00 2001 From: Tim Urista Date: Wed, 15 Jul 2020 07:13:05 -0700 Subject: [PATCH 05/78] Roku for link --- ADOPTERS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 85415a2657..1ed3532262 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,6 +1,6 @@ -| Organization | Contact | Description of Use | -| ------------ | ------- | ------------------ | -| [Spotify](https://www.spotify.com) |[@alund](https://github.com/alund)| Main interface towards all of Spotify's infrastructure and technical documentation.| -| [bol.com](https://www.bol.com) |[@RoyJacobs](https://github.com/RoyJacobs)| Initial work being done to unify platform tooling.| -| [DFDS](https://www.dfds.com) |[@carlsendk](https://github.com/carlsendk)| V2 self-service platform.| -| [DFDS](https://www.roku.com) |[@timurista](https://github.com/timurista)| Initial work on Cloud engineering service platform.| +| Organization | Contact | Description of Use | +| ---------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- | +| [Spotify](https://www.spotify.com) | [@alund](https://github.com/alund) | Main interface towards all of Spotify's infrastructure and technical documentation. | +| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | +| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | +| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | From 1ee8a8052438852e142fd026e3480c3080dc2fe2 Mon Sep 17 00:00:00 2001 From: Tim Urista Date: Wed, 15 Jul 2020 07:13:39 -0700 Subject: [PATCH 06/78] fix copypasta-error --- ADOPTERS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 1ed3532262..26281c2ce5 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,6 +1,6 @@ -| Organization | Contact | Description of Use | -| ---------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- | -| [Spotify](https://www.spotify.com) | [@alund](https://github.com/alund) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | -| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | +| Organization | Contact | Description of Use | +| ------------ | ------- | ------------------ | +| [Spotify](https://www.spotify.com) |[@alund](https://github.com/alund)| Main interface towards all of Spotify's infrastructure and technical documentation.| +| [bol.com](https://www.bol.com) |[@RoyJacobs](https://github.com/RoyJacobs)| Initial work being done to unify platform tooling.| +| [DFDS](https://www.dfds.com) |[@carlsendk](https://github.com/carlsendk)| V2 self-service platform.| +| [Roku](https://www.roku.com) |[@timurista](https://github.com/timurista)| Initial work on Cloud engineering service platform.| From fd9759ed8aa86a58f6cf34bb3df208a3460fc3d4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 15 Jul 2020 18:32:12 +0200 Subject: [PATCH 07/78] cli: identify root package by workspaces config + allow non-monorepo --- packages/cli/src/lib/paths.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/lib/paths.ts b/packages/cli/src/lib/paths.ts index be3f3dcee8..ab3578ca1c 100644 --- a/packages/cli/src/lib/paths.ts +++ b/packages/cli/src/lib/paths.ts @@ -47,7 +47,7 @@ export type Paths = { resolveTargetRoot: ResolveFunc; }; -// Looks for a package.json that has name: "root" to identify the root of the monorepo +// Looks for a package.json with a workspace config to identify the root of the monorepo export function findRootPath(topPath: string): string { let path = topPath; @@ -58,7 +58,7 @@ export function findRootPath(topPath: string): string { if (exists) { try { const data = fs.readJsonSync(packagePath); - if (data.name === 'root' || data.name.includes('backstage-e2e')) { + if (data.workspaces?.packages) { return path; } } catch (error) { @@ -70,9 +70,7 @@ export function findRootPath(topPath: string): string { const newPath = dirname(path); if (newPath === path) { - throw new Error( - `No package.json with name "root" found as a parent of ${topPath}`, - ); + return topPath; // We didn't find any root package.json, assume we're not in a monorepo } path = newPath; } From e888e1865d81a1e147444c85a167989b1a332abe Mon Sep 17 00:00:00 2001 From: Tim Urista Date: Thu, 16 Jul 2020 13:59:09 -0700 Subject: [PATCH 08/78] template has wrong indentation for app yaml kubernetes --- install/kubernetes/app.yaml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/install/kubernetes/app.yaml b/install/kubernetes/app.yaml index ed4108f11b..ca9a50800e 100644 --- a/install/kubernetes/app.yaml +++ b/install/kubernetes/app.yaml @@ -11,17 +11,17 @@ spec: matchLabels: app: backstage component: frontend - template: - metadata: - labels: - app: backstage - component: frontend - spec: - containers: - - name: app - image: spotify/backstage:latest - imagePullPolicy: Always - ports: - - containerPort: 80 - name: app - protocol: TCP + template: + metadata: + labels: + app: backstage + component: frontend + spec: + containers: + - name: app + image: spotify/backstage:latest + imagePullPolicy: Always + ports: + - containerPort: 80 + name: app + protocol: TCP From d96e57877642cf57ad88869144aaaaf7449e92e6 Mon Sep 17 00:00:00 2001 From: Tim Urista Date: Thu, 16 Jul 2020 16:30:20 -0700 Subject: [PATCH 09/78] add indentation and allow for local image --- install/kubernetes/app.yaml | 2 +- install/kubernetes/backend.yaml | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/install/kubernetes/app.yaml b/install/kubernetes/app.yaml index ca9a50800e..ce284afd31 100644 --- a/install/kubernetes/app.yaml +++ b/install/kubernetes/app.yaml @@ -20,7 +20,7 @@ spec: containers: - name: app image: spotify/backstage:latest - imagePullPolicy: Always + imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: app diff --git a/install/kubernetes/backend.yaml b/install/kubernetes/backend.yaml index 669426fecd..bc82731958 100644 --- a/install/kubernetes/backend.yaml +++ b/install/kubernetes/backend.yaml @@ -11,17 +11,17 @@ spec: matchLabels: app: backstage component: backend - template: - metadata: - labels: - app: backstage - component: backend - spec: - containers: - - name: backend - image: spotify/backstage-backend:latest - imagePullPolicy: Always - ports: - - containerPort: 7000 - name: backend - protocol: TCP + template: + metadata: + labels: + app: backstage + component: backend + spec: + containers: + - name: backend + image: spotify/backstage-backend:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 7000 + name: backend + protocol: TCP From 6e8691e9980b0963b2663d095102c121d6e12e63 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 14 Jul 2020 21:04:20 +0200 Subject: [PATCH 10/78] packages: inital dump of docgen + copyright headers --- packages/docgen/.eslintrc.js | 6 + packages/docgen/src/compiler.ts | 111 +++++++ .../docgen/src/docgen/ApiDocGenerator.test.ts | 261 +++++++++++++++ packages/docgen/src/docgen/ApiDocGenerator.ts | 297 ++++++++++++++++++ packages/docgen/src/docgen/ApiDocPrinter.ts | 112 +++++++ packages/docgen/src/docgen/MarkdownPrinter.ts | 136 ++++++++ .../docgen/src/docgen/TypeLocator.test.ts | 68 ++++ packages/docgen/src/docgen/TypeLocator.ts | 129 ++++++++ .../src/docgen/TypescriptHighlighter.ts | 88 ++++++ packages/docgen/src/docgen/flatMap.test.ts | 28 ++ packages/docgen/src/docgen/flatMap.ts | 20 ++ .../docgen/src/docgen/sortSelector.test.ts | 49 +++ packages/docgen/src/docgen/sortSelector.ts | 34 ++ packages/docgen/src/docgen/testUtils.ts | 67 ++++ packages/docgen/src/docgen/types.ts | 96 ++++++ packages/docgen/src/index.ts | 17 + 16 files changed, 1519 insertions(+) create mode 100644 packages/docgen/.eslintrc.js create mode 100644 packages/docgen/src/compiler.ts create mode 100644 packages/docgen/src/docgen/ApiDocGenerator.test.ts create mode 100644 packages/docgen/src/docgen/ApiDocGenerator.ts create mode 100644 packages/docgen/src/docgen/ApiDocPrinter.ts create mode 100644 packages/docgen/src/docgen/MarkdownPrinter.ts create mode 100644 packages/docgen/src/docgen/TypeLocator.test.ts create mode 100644 packages/docgen/src/docgen/TypeLocator.ts create mode 100644 packages/docgen/src/docgen/TypescriptHighlighter.ts create mode 100644 packages/docgen/src/docgen/flatMap.test.ts create mode 100644 packages/docgen/src/docgen/flatMap.ts create mode 100644 packages/docgen/src/docgen/sortSelector.test.ts create mode 100644 packages/docgen/src/docgen/sortSelector.ts create mode 100644 packages/docgen/src/docgen/testUtils.ts create mode 100644 packages/docgen/src/docgen/types.ts create mode 100644 packages/docgen/src/index.ts diff --git a/packages/docgen/.eslintrc.js b/packages/docgen/.eslintrc.js new file mode 100644 index 0000000000..503c048748 --- /dev/null +++ b/packages/docgen/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], + rules: { + 'no-console': 0, + }, +}; diff --git a/packages/docgen/src/compiler.ts b/packages/docgen/src/compiler.ts new file mode 100644 index 0000000000..3a196ca709 --- /dev/null +++ b/packages/docgen/src/compiler.ts @@ -0,0 +1,111 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as ts from 'typescript'; +import { resolve, join, dirname } from 'path'; +import { promisify } from 'util'; +import fs from 'fs'; +import ApiDocGenerator from './docgen/ApiDocGenerator'; +import sortSelector from './docgen/sortSelector'; +import TypeLocator from './docgen/TypeLocator'; +import ApiDocPrinter from './docgen/ApiDocPrinter'; +import TypescriptHighlighter from './docgen/TypescriptHighlighter'; +import MarkdownPrinter from './docgen/MarkdownPrinter'; +import { sync as mkdirpSync } from 'mkdirp'; + +const writeFile = promisify(fs.writeFile); + +function loadOptions(path: string): ts.CompilerOptions { + let { extends: parent, compilerOptions }: any = require(path); + + if (!parent) { + return compilerOptions; + } + if (parent.startsWith('.')) { + parent = join(dirname(path), parent); + } + + return { ...loadOptions(parent), ...compilerOptions }; +} + +async function main() { + const rootDir = resolve(__dirname, '..'); + const srcDir = resolve(rootDir, 'src'); + const entrypoint = resolve(srcDir, 'index.js'); + const apiRefsDir = resolve(rootDir, 'docgen', 'build', 'api-refs'); + const mkdocsYaml = resolve(apiRefsDir, 'mkdocs.yml'); + + process.chdir(rootDir); + + const options = loadOptions('../tsconfig.json'); + + delete options.moduleResolution; + options.removeComments = false; + options.noEmit = true; + + const program = ts.createProgram([entrypoint], options); + + const typeLocator = TypeLocator.fromProgram(program); + + const { apis } = typeLocator.findExportedInstances({ + apis: typeLocator.getExportedType( + resolve(srcDir, 'core', 'api', 'ApiRef.ts'), + ), + }); + + const apiDocGenerator = ApiDocGenerator.fromProgram(program, rootDir, srcDir); + + const apiDocs = apis + .map(api => { + try { + return apiDocGenerator.toDoc(api); + } catch (error) { + throw new Error( + `Doc generation failed for API in ${api.source.fileName}, ${error.stack}`, + ); + } + }) + .sort(sortSelector(x => x.id)); + + const apiDocPrinter = new ApiDocPrinter( + () => new MarkdownPrinter(new TypescriptHighlighter()), + ); + + mkdirpSync(resolve(apiRefsDir, 'docs')); + + await Promise.all( + apiDocs.map(apiDoc => { + const data = apiDocPrinter.print(apiDoc); + + return writeFile(join(apiRefsDir, 'docs', `${apiDoc.name}.md`), data); + }), + ); + + fs.writeFileSync( + mkdocsYaml, + [ + 'site_name: api-references', + 'nav:', + ...apiDocs.map(({ id, name }) => ` - ${id}: '${name}.md'`), + ].join('\n'), + 'utf8', + ); +} + +main().catch(error => { + console.error(error.stack || error); + process.exit(1); +}); diff --git a/packages/docgen/src/docgen/ApiDocGenerator.test.ts b/packages/docgen/src/docgen/ApiDocGenerator.test.ts new file mode 100644 index 0000000000..f30c67288d --- /dev/null +++ b/packages/docgen/src/docgen/ApiDocGenerator.test.ts @@ -0,0 +1,261 @@ +/* + * 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 ts from 'typescript'; +import TypeLocator from './TypeLocator'; +import { createMemProgram } from './testUtils'; +import ApiDocGenerator from './ApiDocGenerator'; + +describe('ApiDocGenerator', () => { + it('should generate empty API doc', () => { + const program = createMemProgram( + ` + import MyApi from './type'; + + type MyApiType = {}; + + export const myApi = new MyApi({ + id: 'my-id', + description: 'my-description', + }); + `, + { + '/mem/type.ts': `export default class MyApi { + constructor(private readonly info: { id: string, description: string }) {} + }`, + }, + ); + + const typeLocator = TypeLocator.fromProgram(program); + + const { apiInstances } = typeLocator.findExportedInstances({ + apiInstances: typeLocator.getExportedType('/mem/type.ts'), + }); + + expect(apiInstances.length).toBe(1); + const [apiInstance] = apiInstances; + + const docGenerator = ApiDocGenerator.fromProgram(program, '/mem', '/mem'); + const doc = docGenerator.toDoc(apiInstance); + + expect(doc.id).toBe('my-id'); + expect(doc.description).toBe('my-description'); + expect(doc.name).toBe('myApi'); + expect(doc.source.fileName).toBe('/mem/index.ts'); + expect(doc.interfaceInfo.dependentTypes).toEqual([]); + expect(doc.interfaceInfo.docs).toEqual([]); + expect(doc.interfaceInfo.file).toBe('index.ts'); + expect(doc.interfaceInfo.lineInFile).toBe(4); + expect(doc.interfaceInfo.members).toEqual([]); + expect(doc.interfaceInfo.name).toBe('MyApiType'); + }); + + it('should generate API docs', () => { + const program = createMemProgram( + ` + import MyApi from './type'; + + /** MySubSubType Docs */ + type MySubSubType = { n: number; }; + + /** MySubType Docs */ + type MySubType = { + /** Field a docs */ + a: boolean; + // Field b docs + b: MySubSubType; + } + + /** MySecondSubType Docs */ + + /** With multiple comments */ + export type MySecondSubType = { s: string }; + + // MyThirdSubType Docs that shouldn't show up + type MyThirdSubType = { b: boolean }; + + /** MyApiType Docs */ + type MyApiType = { + /** Docs for x */ + x: string; + // Line comments shouldn't show up + y: MySubType; + /** Multiple */ + /** JsDoc */ + /** Comments */ + z(a: Promise): Array; + }; + + /** Should not show up */ + export const myApi = new MyApi({ + id: 'my-id', + description: 'my-description', + }); + `, + { + '/mem/type.ts': `export default class MyApi { + constructor(private readonly info: { id: string, description: string }) {} + }`, + }, + ); + + const source = program.getSourceFile('/mem/index.ts'); + + // Figure out type IDs so we can make sure they match later + const checker = program.getTypeChecker(); + const symbols = checker.getSymbolsInScope( + source!.getChildren().slice(-1)[0], + ts.SymbolFlags.TypeAlias, + ); + const Ids = [ + 'MySubType', + 'MySubSubType', + 'MySecondSubType', + 'MyThirdSubType', + ].reduce((ids, name) => { + const symbol = symbols.find(s => s.escapedName === name)!; + const type = checker.getTypeAtLocation(symbol.declarations[0]); + ids[name] = (type.aliasSymbol as any).id; + return ids; + }, {} as { [key in string]: number }); + + const typeLocator = TypeLocator.fromProgram(program); + + const { apiInstances } = typeLocator.findExportedInstances({ + apiInstances: typeLocator.getExportedType('/mem/type.ts'), + }); + + expect(apiInstances.length).toBe(1); + const [apiInstance] = apiInstances; + + const docGenerator = ApiDocGenerator.fromProgram(program, '/mem', '/mem'); + const doc = docGenerator.toDoc(apiInstance); + + expect(doc.id).toBe('my-id'); + expect(doc.description).toBe('my-description'); + expect(doc.name).toBe('myApi'); + expect(doc.source.fileName).toBe('/mem/index.ts'); + expect(doc.interfaceInfo.docs).toEqual(['MyApiType Docs']); + expect(doc.interfaceInfo.file).toBe('index.ts'); + expect(doc.interfaceInfo.lineInFile).toBe(24); + expect(doc.interfaceInfo.name).toBe('MyApiType'); + expect(doc.interfaceInfo.members).toEqual([ + { + type: 'prop', + name: 'x', + path: 'MyApiType.x', + text: 'x: string', + docs: ['Docs for x'], + links: [], + }, + { + type: 'prop', + name: 'y', + path: 'MyApiType.y', + text: 'y: MySubType', + docs: [], + links: [ + { + id: Ids.MySubType, + name: 'MySubType', + path: 'index.ts/MySubType', + location: [3, 12], + }, + ], + }, + { + type: 'method', + name: 'z', + path: 'MyApiType.z', + text: + 'z(a: Promise): Array', + docs: ['Multiple', 'JsDoc', 'Comments'], + links: [ + { + id: Ids.MySecondSubType, + name: 'MySecondSubType', + path: 'index.ts/MySecondSubType', + location: [27, 42], + }, + { + id: Ids.MyThirdSubType, + name: 'MyThirdSubType', + path: 'index.ts/MyThirdSubType', + location: [56, 70], + }, + ], + }, + ]); + expect(doc.interfaceInfo.dependentTypes).toEqual([ + { + id: Ids.MySubType, + name: 'MySubType', + path: 'index.ts/MySubType', + file: 'index.ts', + lineInFile: 8, + text: `type MySubType = { + /** Field a docs */ + a: boolean; + // Field b docs + b: MySubSubType; + }`, + docs: ['MySubType Docs'], + links: [ + { + id: Ids.MySubSubType, + name: 'MySubSubType', + path: 'index.ts/MySubSubType', + location: [102, 114], + }, + ], + children: [], + }, + { + id: Ids.MySubSubType, + name: 'MySubSubType', + path: 'index.ts/MySubSubType', + file: 'index.ts', + lineInFile: 5, + text: 'type MySubSubType = { n: number; }', + docs: ['MySubSubType Docs'], + links: [], + children: [], + }, + { + id: Ids.MySecondSubType, + name: 'MySecondSubType', + path: 'index.ts/MySecondSubType', + file: 'index.ts', + lineInFile: 18, + text: 'export type MySecondSubType = { s: string }', + docs: ['MySecondSubType Docs', 'With multiple comments'], + links: [], + children: [], + }, + { + id: Ids.MyThirdSubType, + name: 'MyThirdSubType', + path: 'index.ts/MyThirdSubType', + file: 'index.ts', + lineInFile: 21, + text: 'type MyThirdSubType = { b: boolean }', + docs: [], + links: [], + children: [], + }, + ]); + }); +}); diff --git a/packages/docgen/src/docgen/ApiDocGenerator.ts b/packages/docgen/src/docgen/ApiDocGenerator.ts new file mode 100644 index 0000000000..e1d1e0f5f1 --- /dev/null +++ b/packages/docgen/src/docgen/ApiDocGenerator.ts @@ -0,0 +1,297 @@ +/* + * 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 ts from 'typescript'; +import { relative } from 'path'; +import { + ExportedInstance, + ApiDoc, + InterfaceInfo, + FieldInfo, + TypeInfo, + TypeLink, +} from './types'; +import { flatMap } from './flatMap'; + +/** + * The ApiDocGenerator uses the typescript compiler API to build the data structure that + * describes a Backstage API and all of it's related types. + * + * It receives an exported instance that of the form + * `export name = new Api({id: ..., description: ...})`. + * It will then traverse all fields and methods on the interface type, and also + * types and declaration of all types that are used by those, both directly and indirectly. + * While traversing, it collects information such as names, location in source, docs, etc. + */ +export default class ApiDocGenerator { + static fromProgram( + program: ts.Program, + basePath: string, + sourcePath: string, + ) { + return new ApiDocGenerator(program.getTypeChecker(), basePath, sourcePath); + } + + constructor( + private readonly checker: ts.TypeChecker, + private readonly basePath: string, + private readonly sourcePath: string, + ) {} + + toDoc(apiInstance: ExportedInstance): ApiDoc { + const { name, source, args, typeArgs } = apiInstance; + + const [info] = args; + if (!ts.isObjectLiteralExpression(info)) { + throw new Error('api info is not an object literal'); + } + + const id = this.getObjectPropertyLiteral(info, 'id'); + const description = this.getObjectPropertyLiteral(info, 'description'); + const interfaceInfo = this.getInterfaceInfo(typeArgs[0]); + if (!interfaceInfo) { + throw new Error('api has no info type argument'); + } + + return { id, name, source, description, interfaceInfo }; + } + + private getNodeDocs(node: ts.Node): string[] { + const docNodes = ((node && (node as any).jsDoc) || []) as ts.JSDoc[]; + const docs = docNodes.map(docNode => docNode.comment || '').filter(Boolean); + return docs; + } + + private getInterfaceInfo(typeNode: ts.TypeNode): InterfaceInfo { + if (ts.isTypeQueryNode(typeNode)) { + throw new Error( + 'APIs must have a proper type parameter, TypeQueries are now allowed', + ); + } + if (!ts.isTypeReferenceNode(typeNode)) { + throw new Error('Interface is not a type node'); + } + + const type = this.checker.getTypeFromTypeNode(typeNode); + const interfaceMembers = type.symbol.members as Map; + if (!interfaceMembers) { + throw new Error('Interface does not have any members'); + } + 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 { line } = sourceFile.getLineAndCharacterOfPosition( + declaration.getStart(), + ); + const docs = this.getNodeDocs(declaration); + + const membersAndTypes = flatMap( + Array.from(interfaceMembers.values()), + fieldSymbol => this.getMemberInfo(name, fieldSymbol), + ); + + const members = membersAndTypes.map(t => t.member); + const dependentTypes = this.flattenTypes( + flatMap(membersAndTypes, t => t.dependentTypes), + ); + + return { name, docs, file, lineInFile: line + 1, members, dependentTypes }; + } + + private getObjectPropertyLiteral( + objectLiteral: ts.ObjectLiteralExpression, + propertyName: string, + ): string { + const prop = objectLiteral.properties.filter( + prop => + ts.isPropertyAssignment(prop) && + ts.isIdentifier(prop.name) && + prop.name.text === propertyName, + )[0] as ts.PropertyAssignment; + + if (!prop) { + throw new Error(`no identifier found for property ${propertyName}`); + } + + const { initializer } = prop; + if (!ts.isStringLiteral(initializer)) { + throw new Error(`no string literal for ${propertyName}`); + } + + return initializer.text; + } + + private getMemberInfo( + parentName: string, + symbol: ts.Symbol, + ): { member: FieldInfo; dependentTypes: TypeInfo[] } { + const declaration = symbol.valueDeclaration; + + const { links, infos: dependentTypes } = this.findAllTypeReferences( + declaration, + ); + + let type: FieldInfo['type'] = 'prop'; + if ( + ts.isPropertySignature(declaration) && + declaration.type && + ts.isFunctionTypeNode(declaration.type) + ) { + type = 'method'; + } else if (ts.isMethodSignature(declaration)) { + type = 'method'; + } + + return { + member: { + type, + name: symbol.name, + path: `${parentName}.${symbol.name}`, + text: declaration.getText().replace(/;$/, ''), + docs: this.getNodeDocs(declaration), + links: this.recalculateLinkOffsets(declaration, links), + }, + dependentTypes, + }; + } + + private findAllTypeReferences = ( + node: ts.Node | undefined, + visited: Set = new Set(), + ): { links: TypeLink[]; infos: TypeInfo[] } => { + if (!node || visited.has(node)) { + return { links: [], infos: [] }; + } + // This makes sure we don't end up in a loop. + // It doesn't exclude repeated types, e.g. Array>, since we're exiting on duplicate nodes, not types. + visited.add(node); + + const children = flatMap(node.getChildren(), child => + this.findAllTypeReferences(child, visited), + ); + + if (!ts.isTypeReferenceNode(node)) { + return { + links: flatMap(children, child => child.links), + infos: flatMap(children, child => child.infos), + }; + } + + const type = this.checker.getTypeFromTypeNode(node); + + const info = this.validateTypeDeclaration(type); + if (!info) { + return { + links: flatMap(children, child => child.links), + infos: flatMap(children, child => child.infos), + }; + } + const { symbol, declaration } = info; + + const typeRefs = this.findAllTypeReferences(declaration, visited); + + const sourceFile = declaration.getSourceFile(); + const { line } = sourceFile.getLineAndCharacterOfPosition( + declaration.getStart(), + ); + const file = relative(this.basePath, sourceFile.fileName); + const typeInfo = { + id: (symbol as any).id, + name: symbol.name, + text: declaration.getText().replace(/;$/, ''), + docs: this.getNodeDocs(declaration), + path: `${file}/${symbol.name}`, + file, + lineInFile: line + 1, // TS line is 0-based + links: this.recalculateLinkOffsets(declaration, typeRefs.links), + children: typeRefs.infos, + }; + + const link = { + id: typeInfo.id, + path: typeInfo.path, + name: typeInfo.name, + location: [node.getStart(), node.getEnd()] as const, + }; + + return { + links: [link, ...flatMap(children, child => child.links)], + infos: [typeInfo, ...flatMap(children, child => child.infos)], + }; + }; + + private validateTypeDeclaration( + type: ts.Type | undefined, + ): undefined | { symbol: ts.Symbol; declaration: ts.Declaration } { + if (!type) { + return; + } + const symbol = type.aliasSymbol || type.symbol; + if (!symbol) { + return; + } + + const [declaration] = symbol.declarations; + + // Don't generate standalone infos for type paramters + if (ts.isTypeParameterDeclaration(declaration)) { + return; + } + + if (!declaration.getSourceFile().fileName.startsWith(this.sourcePath)) { + return; + } + + return { symbol, declaration }; + } + + private flattenTypes(descs: TypeInfo[]): TypeInfo[] { + function getChildren(parent: TypeInfo): TypeInfo[] { + return [ + { + ...parent, + children: [], + }, + ...flatMap(parent.children, getChildren), + ]; + } + + const flatDescs = flatMap(descs, getChildren); + const seenTypes = new Set(); + return flatDescs.filter(desc => { + if (seenTypes.has(desc.id)) { + return false; + } + seenTypes.add(desc.id); + return true; + }); + } + + private recalculateLinkOffsets( + parent: ts.Node, + links: TypeLink[], + ): TypeLink[] { + const parentStart = parent.getStart(); + return links.map(link => { + const [start, end] = link.location; + return { + ...link, + location: [start - parentStart, end - parentStart], + }; + }); + } +} diff --git a/packages/docgen/src/docgen/ApiDocPrinter.ts b/packages/docgen/src/docgen/ApiDocPrinter.ts new file mode 100644 index 0000000000..5824047ef7 --- /dev/null +++ b/packages/docgen/src/docgen/ApiDocPrinter.ts @@ -0,0 +1,112 @@ +/* + * 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 { execSync } from 'child_process'; +import MarkdownPrinter from './MarkdownPrinter'; +import sortSelector from './sortSelector'; +import { ApiDoc } from './types'; + +const GH_BASE_URL = 'https://github.com/spotify/backstage'; + +const COMMIT_SHA = + process.env.COMMIT_SHA || execSync('git rev-parse HEAD').toString('utf8'); + +/** + * The ApiDocPrinter takes a ApiDoc data structure, typically generated by an ApiDocGenerator, + * and prints it out as a markdown doc with custom code highlighting and links. + */ +export default class ApiDocPrinter { + printerFactory: () => MarkdownPrinter; + + constructor(printerFactory: () => MarkdownPrinter) { + this.printerFactory = printerFactory; + } + + mkTypeLink({ file, lineInFile }: { file: string; lineInFile: number }) { + const text = `${file}:${lineInFile}`; + const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${file}#L${lineInFile}`; + return `[${text}](${href}){:target="_blank"}`; + } + + print(apiDoc: ApiDoc): Buffer { + const printer = this.printerFactory(); + + // Remove line numbers from codeblocks + printer.style('.linenodiv{ display: none }'); + + printer.header(1, `shared/apis/${apiDoc.id}`); + + const ifInfo = apiDoc.interfaceInfo; + + if (ifInfo.docs.length) { + for (const doc of ifInfo.docs) { + printer.text(doc); + } + } else { + printer.paragraph(apiDoc.description); + } + + printer.header(2, 'API Interface'); + printer.paragraph( + `The API interface type is defined at ${this.mkTypeLink(ifInfo)} as ${ + ifInfo.name + }.`, + ); + printer.paragraph('All members of the interface are listed below.'); + + for (const member of ifInfo.members) { + printer.header( + 3, + `${member.name}${member.type === 'method' ? '()' : ''}`, + member.path, + ); + + for (const doc of member.docs) { + printer.text(doc); + } + + printer.codeWithLinks(member); + } + + if (ifInfo.dependentTypes.length) { + printer.header(2, 'Types'); + } + for (const type of ifInfo.dependentTypes + .slice() + .sort(sortSelector(x => x.name))) { + printer.header(3, `${type.name}`, type.path); + + for (const doc of type.docs) { + printer.text(doc); + } + printer.codeWithLinks(type); + + printer.paragraph(`Defined at ${this.mkTypeLink(type)}.`); + + const usageLinks = [...ifInfo.members, ...ifInfo.dependentTypes] + .filter(member => { + return member.links.some(link => link.id === type.id); + }) + .map(({ name, path }) => `[${name}](#${path})`); + + if (usageLinks.length) { + printer.paragraph(`Referenced by: ${usageLinks.join(', ')}.`); + } + } + + return printer.toBuffer(); + } +} diff --git a/packages/docgen/src/docgen/MarkdownPrinter.ts b/packages/docgen/src/docgen/MarkdownPrinter.ts new file mode 100644 index 0000000000..c33ecc76a8 --- /dev/null +++ b/packages/docgen/src/docgen/MarkdownPrinter.ts @@ -0,0 +1,136 @@ +/* + * 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 sortSelector from './sortSelector'; +import { Highlighter, TypeLink } from './types'; + +/** + * The MarkdownPrinter is a helper class for building markdown documents. + */ +export default class MarkdownPrinter { + private str: string = ''; + private readonly highlighter: Highlighter; + + constructor(highlighter: Highlighter) { + this.highlighter = highlighter; + } + + private line(line: string = '') { + this.str += `${line}\n`; + } + + text(text: string) { + this.line(text); + this.line(); + } + + style(style: string) { + this.line(''); + } + + header(level: number, text: string, id?: string) { + this.line(`${'#'.repeat(level)} ${text}${id ? ` {#${id}}` : ''}`); + this.line(); + } + + paragraph(text: string) { + this.line( + text + .trim() + .split('\n') + .map(line => line.trim()) + .join('\n'), + ); + this.line(); + } + + code(syntax: string = '', block: string) { + this.line(`\`\`\`${syntax}`); + this.line(block); + this.line('```'); + this.line(); + } + + codeWithLinks({ text, links = [] }: { text: string; links?: TypeLink[] }) { + this.line('
'); + this.line( + '
', + ); + this.line('
');
+    this.line(this.formatWithLinks({ text, links }));
+    this.line('
'); + this.line('
'); + this.line('
'); + } + + private escapeText: (text: string) => string = (() => { + const escapes: { [char in string]: string } = { + '&': '&', + '<': '<', + '>': '>', + }; + + return (text: string) => text.replace(/[&<>]/g, char => escapes[char]); + })(); + + private formatWithLinks({ + text, + links = [], + }: { + text: string; + links?: TypeLink[]; + }): string { + const sortedLinks = links.slice().sort(sortSelector(x => x.location[0])); + + sortedLinks.reduce((lastEnd, link) => { + if (link.location[0] <= lastEnd) { + throw new Error( + `overlapping link detected for ${link.path}, ${link.location}`, + ); + } + return link.location[1]; + }, -1); + + const parts: Array<{ text: string; path?: string }> = []; + + const end = sortedLinks.reduce((prev, link) => { + const [start, end] = link.location; + parts.push( + { text: text.slice(prev, start) }, + { text: text.slice(start, end), path: link.path }, + ); + return end; + }, 0); + + parts.push({ text: text.slice(end) }); + + return parts + .map(part => { + if (part.path) { + return `${this.escapeText(part.text)}`; + } else { + return this.highlighter.highlight(this.escapeText(part.text)); + } + }) + .join(''); + } + + toBuffer(): Buffer { + return Buffer.from(this.str, 'utf8'); + } +} diff --git a/packages/docgen/src/docgen/TypeLocator.test.ts b/packages/docgen/src/docgen/TypeLocator.test.ts new file mode 100644 index 0000000000..651fbf13b2 --- /dev/null +++ b/packages/docgen/src/docgen/TypeLocator.test.ts @@ -0,0 +1,68 @@ +/* + * 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 ts from 'typescript'; +import TypeLocator from './TypeLocator'; +import { createMemProgram } from './testUtils'; + +describe('TypeLocator', () => { + it('should find a default export', () => { + const program = createMemProgram('export default class MyApi {}'); + + const typeLocator = TypeLocator.fromProgram(program); + + const apiType = typeLocator.getExportedType('/mem/index.ts'); + expect(apiType.symbol.name).toBe('default'); + const [declaration] = apiType.symbol.declarations; + expect(declaration.kind).toBe(ts.SyntaxKind.ClassDeclaration); + expect((declaration as ts.ClassDeclaration).name).toBeDefined(); + expect((declaration as ts.ClassDeclaration).name!.text).toBe('MyApi'); + }, 10000); + + it('should find api instance export', () => { + const program = createMemProgram( + ` + import MyApi from './type'; + + type MyApiType = {}; + + export const myApi = new MyApi(); + `, + { + '/mem/type.ts': 'export default class MyApi {}', + }, + ); + + const typeLocator = TypeLocator.fromProgram(program); + + const { apiInstances } = typeLocator.findExportedInstances({ + apiInstances: typeLocator.getExportedType('/mem/type.ts'), + }); + + expect(apiInstances.length).toBe(1); + const [apiInstance] = apiInstances; + expect(apiInstance.name).toBe('myApi'); + expect(apiInstance.source.fileName).toBe('/mem/index.ts'); + expect(apiInstance.args).toEqual([]); + + expect(apiInstance.typeArgs.length).toBe(1); + const [typeArg] = apiInstance.typeArgs; + expect(typeArg.kind).toBe(ts.SyntaxKind.TypeReference); + expect((typeArg as ts.TypeReferenceNode).typeName.getText()).toBe( + 'MyApiType', + ); + }); +}); diff --git a/packages/docgen/src/docgen/TypeLocator.ts b/packages/docgen/src/docgen/TypeLocator.ts new file mode 100644 index 0000000000..8342548e01 --- /dev/null +++ b/packages/docgen/src/docgen/TypeLocator.ts @@ -0,0 +1,129 @@ +/* + * 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 ts from 'typescript'; +import { ExportedInstance } from './types'; + +/** + * The TypeLocator is used to extract typescrint Type structures from a compiled program, + * as well as finding locations where types are used in according to a matching pattern. + * + * This is used to e.g. find exported APIs that we should generate documentation for. + */ +export default class TypeLocator { + private readonly checker: ts.TypeChecker; + private readonly program: ts.Program; + + static fromProgram(program: ts.Program) { + return new TypeLocator(program.getTypeChecker(), program); + } + + constructor(checker: ts.TypeChecker, program: ts.Program) { + this.checker = checker; + this.program = program; + } + + getExportedType(path: string, exportedName: string = 'default'): ts.Type { + const source = this.program.getSourceFile(path); + if (!source) { + throw new Error(`Source not found for path '${path}'`); + } + const exported = this.checker.getExportsOfModule( + this.checker.getSymbolAtLocation(source)!, + ); + const [symbol] = exported.filter(e => e.name === exportedName); + if (!symbol) { + throw new Error(`No export '${exportedName}' found in ${path}`); + } + const type = this.checker.getTypeOfSymbolAtLocation(symbol, source); + return type; + } + + findExportedInstances( + typeLookupTable: { [key in T]: ts.Type }, + ): { [key in T]: ExportedInstance[] } { + const docMap = new Map(); + for (const type of Object.values(typeLookupTable)) { + docMap.set(type, []); + } + + this.program.getSourceFiles().forEach(source => { + ts.forEachChild(source, node => { + const decl = this.getExportedConstructorDeclaration(node); + if (!decl || !docMap.has(decl.constructorType)) { + return; + } + + docMap.get(decl.constructorType)!.push({ + node, + name: decl.name, + source, + args: Array.from(decl.initializer.arguments || []), + typeArgs: Array.from(decl.initializer.typeArguments || []), + }); + }); + }); + + const result: { [key in T]?: ExportedInstance[] } = {}; + + for (const key in typeLookupTable) { + if (typeLookupTable.hasOwnProperty(key)) { + const type = typeLookupTable[key]; + result[key] = docMap.get(type)!; + } + } + + return result as { [key in T]: ExportedInstance[] }; + } + + private getExportedConstructorDeclaration( + node: ts.Node, + ): + | { constructorType: ts.Type; initializer: ts.NewExpression; name: string } + | undefined { + if (!ts.isVariableStatement(node)) { + return; + } + if ( + !node.modifiers || + !node.modifiers.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword) + ) { + return; + } + const { declarations } = node.declarationList; + if (declarations.length !== 1) { + return; + } + + const [declaration] = declarations; + const { initializer, name } = declaration; + + if (!initializer || !name) { + return; + } + if (!ts.isNewExpression(initializer)) { + return; + } + if (!ts.isIdentifier(name)) { + return; + } + + const constructorType = this.checker.getTypeAtLocation( + initializer.expression, + ); + return { constructorType, initializer, name: name.text }; + } +} diff --git a/packages/docgen/src/docgen/TypescriptHighlighter.ts b/packages/docgen/src/docgen/TypescriptHighlighter.ts new file mode 100644 index 0000000000..a16c9a2c28 --- /dev/null +++ b/packages/docgen/src/docgen/TypescriptHighlighter.ts @@ -0,0 +1,88 @@ +/* + * 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 { Highlighter } from './types'; +import { flatMap } from './flatMap'; + +// Simple syntax highlighter that mimics hilite +export default class TypescriptHighlighter implements Highlighter { + private static readonly basicTypes = [ + 'boolean', + 'number', + 'string', + 'Array', + 'object', + 'Record', + 'Set', + 'Map', + 'true', + 'false', + 'null', + 'undefined', + 'void', + 'Promise', + 'any', + '[0-9\\.]+', + ]; + + // List of highlightings to apply, each with a match regex and the style that should be applied + private static readonly highlighters = [ + [/(\/\*\*?(?:.|\n)+?\*\/)/g, 'color: #60a0b0; font-style: italic'], // block comment + [/(\/\/.*)/gm, 'color: #60a0b0; font-style: italic'], // line comment + [/('[^']*?'|"[^"]*?")/g, 'color: #4070a0'], // string literals + [ + new RegExp( + `\\b(${TypescriptHighlighter.basicTypes.join('|')})\\b(?!:|,)`, + 'g', + ), + 'color: #902000', + ], // basic types + [ + /^((?:export\s)?(?:type\s)?(?:interface\s)?)/g, + 'color: #007020; font-weight: bold', + ], // keywords + ] as readonly [RegExp, string][]; + + highlight(text: string): string { + // Each part is either plain text that can be highlighted or text that is already highlighted + type HighlightPart = { text: string; highlighted?: boolean }; + + const painter = (regex: RegExp, style: string) => (part: HighlightPart) => { + if (part.highlighted) { + return [part]; + } + // Apply highlighting to all matches of the regex by splitting into parts + return part.text.split(regex).map((text, index) => { + // Odd parts are the ones that matched the regex and should be highlighted. + if (index % 2 === 1) { + return { + text: `${text}`, + highlighted: true, + }; + } + return { text }; + }); + }; + + // Order here is important, e.g. comments must be first to avoid string literals inside comments being highlighted + return TypescriptHighlighter.highlighters + .reduce((parts, highlighter) => flatMap(parts, painter(...highlighter)), [ + { text }, + ]) + .map(({ text }) => text) + .join(''); + } +} diff --git a/packages/docgen/src/docgen/flatMap.test.ts b/packages/docgen/src/docgen/flatMap.test.ts new file mode 100644 index 0000000000..6186863039 --- /dev/null +++ b/packages/docgen/src/docgen/flatMap.test.ts @@ -0,0 +1,28 @@ +/* + * 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 { flatMap } from './flatMap'; + +describe('flatMap', () => { + it('should flat map', () => { + expect(flatMap([], x => x)).toEqual([]); + expect(flatMap([[]], x => x)).toEqual([]); + // This simple implementation only flattens once + expect(flatMap([[[]]], x => x)).toEqual([[]]); + + expect(flatMap([[1, 2], 3, [4, 5]], x => x)).toEqual([1, 2, 3, 4, 5]); + }); +}); diff --git a/packages/docgen/src/docgen/flatMap.ts b/packages/docgen/src/docgen/flatMap.ts new file mode 100644 index 0000000000..bdea672a41 --- /dev/null +++ b/packages/docgen/src/docgen/flatMap.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function flatMap(array: T[], func: (t: T) => R[] | R): R[] { + const out = array.map(func); + return ([] as R[]).concat.apply([], out as R[]); +} diff --git a/packages/docgen/src/docgen/sortSelector.test.ts b/packages/docgen/src/docgen/sortSelector.test.ts new file mode 100644 index 0000000000..a3da3e2950 --- /dev/null +++ b/packages/docgen/src/docgen/sortSelector.test.ts @@ -0,0 +1,49 @@ +/* + * 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 sortSelector from './sortSelector'; + +describe('sortSelector', () => { + it('should stable sort', () => { + const arr = [ + [3, 1], + [1, 2], + [1, 1], + [1, 3], + [2, 1], + ]; + + const sortedByFirst = arr.slice().sort(sortSelector(([first]) => first)); + expect(sortedByFirst).toEqual([ + [1, 2], + [1, 1], + [1, 3], + [2, 1], + [3, 1], + ]); + + const sortedBySecond = arr + .slice() + .sort(sortSelector(([, second]) => second)); + expect(sortedBySecond).toEqual([ + [3, 1], + [1, 1], + [2, 1], + [1, 2], + [1, 3], + ]); + }); +}); diff --git a/packages/docgen/src/docgen/sortSelector.ts b/packages/docgen/src/docgen/sortSelector.ts new file mode 100644 index 0000000000..1ba7a7918d --- /dev/null +++ b/packages/docgen/src/docgen/sortSelector.ts @@ -0,0 +1,34 @@ +/* + * 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. + */ + +/** + * The sortSelector is a utility that makes a sort function by selecting the value to sort on with a lambda. + */ +export default function sortSelector( + selector: (x: T) => any, +): (a: T, b: T) => -1 | 1 | 0 { + return (a: T, b: T) => { + let aV = selector(a); + let bV = selector(b); + if (aV < bV) { + return -1; + } else if (aV > bV) { + return 1; + } else { + return 0; + } + }; +} diff --git a/packages/docgen/src/docgen/testUtils.ts b/packages/docgen/src/docgen/testUtils.ts new file mode 100644 index 0000000000..552776016a --- /dev/null +++ b/packages/docgen/src/docgen/testUtils.ts @@ -0,0 +1,67 @@ +/* + * 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 ts from 'typescript'; + +export function createMemProgram( + indexSource: string, + otherSourceFiles: { [fileName in string]: string } = {}, +): ts.Program { + const rootDir = '/mem'; + + const options = { noEmit: true }; + const baseHost = ts.createCompilerHost(options); + const files: { [fileName in string]: string } = { + [`${rootDir}/index.ts`]: indexSource, + ...otherSourceFiles, + }; + + // Custom compiler hosts that reads from a map of in-memory files, but + // falls back to reading from disc for ts libs etc. + const compilerHost: ts.CompilerHost = { + ...baseHost, + readFile(fileName): string | undefined { + if (fileName in files) { + return files[fileName]; + } + return baseHost.readFile(fileName); + }, + getCurrentDirectory(): string { + return rootDir; + }, + directoryExists(dir) { + if (dir === rootDir) { + return true; + } + if (baseHost.directoryExists) { + return baseHost.directoryExists(dir); + } + return false; + }, + fileExists(fileName: string): boolean { + return !!files[fileName] || baseHost.fileExists(fileName); + }, + getSourceFile(fileName, ...rest): ts.SourceFile | undefined { + const file = files[fileName]; + if (file) { + return ts.createSourceFile(fileName, file, ts.ScriptTarget.ES2017); + } + return baseHost.getSourceFile(fileName, ...rest); + }, + }; + + return ts.createProgram(['/mem/index.ts'], options, compilerHost); +} diff --git a/packages/docgen/src/docgen/types.ts b/packages/docgen/src/docgen/types.ts new file mode 100644 index 0000000000..df9a5b93a0 --- /dev/null +++ b/packages/docgen/src/docgen/types.ts @@ -0,0 +1,96 @@ +/* + * 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 ts from 'typescript'; + +/** + * A TypeLink is a link to a different type. + */ +export type TypeLink = { + // The ID of the linked type + id: number; + // The path to the type from the project root + path: string; + // The name of the type, to display + name: string; + // The location of the type name as it appears in it's parent text. + location: readonly [number, number]; +}; + +/** + * TypeInfo describes a Typescript Type. + */ +export type TypeInfo = { + id: number; + name: string; + path: string; + file: string; + lineInFile: number; + text: string; + docs: string[]; + links: TypeLink[]; + children: TypeInfo[]; +}; + +/** + * FieldInfo describes a property or method in a documented API. + */ +export type FieldInfo = { + type: 'prop' | 'method'; + name: string; + path: string; + text: string; + docs: string[]; + links: TypeLink[]; +}; + +/** + * InterfaceInfo describes the type of a documented API. + */ +export type InterfaceInfo = { + name: string; + docs: string[]; + file: string; + lineInFile: number; + members: Array; + dependentTypes: TypeInfo[]; +}; + +/** + * ApiDoc describes a documented API. + */ +export type ApiDoc = { + id: string; + name: string; + source: ts.SourceFile; + description: string; + interfaceInfo: InterfaceInfo; +}; + +/** + * ExportedInstance describes an expression matching `export {name} = new {Contructor}<{typeArgs}...>({args}...)` + */ +export type ExportedInstance = { + node: ts.Node; + name: string; + source: ts.SourceFile; + args: Array; + typeArgs: Array; +}; + +export type Highlighter = { + highlight(test: string): string; +}; diff --git a/packages/docgen/src/index.ts b/packages/docgen/src/index.ts new file mode 100644 index 0000000000..d57c49f9f8 --- /dev/null +++ b/packages/docgen/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import './compiler'; From 1fc5f9f468ee013f336ac1b184f46e9f39429d18 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 14 Jul 2020 21:11:46 +0200 Subject: [PATCH 11/78] docgen: add package.json and bin entrypoint --- packages/docgen/bin/backstage-docgen | 34 +++++++++++++++++++ packages/docgen/package.json | 49 ++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100755 packages/docgen/bin/backstage-docgen create mode 100644 packages/docgen/package.json diff --git a/packages/docgen/bin/backstage-docgen b/packages/docgen/bin/backstage-docgen new file mode 100755 index 0000000000..2b445ca9af --- /dev/null +++ b/packages/docgen/bin/backstage-docgen @@ -0,0 +1,34 @@ +#!/usr/bin/env node +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require('path'); + +// Figure out whether we're running inside the backstage repo or as an installed dependency +const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src')); + +if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) { + require('..'); +} else { + require('ts-node').register({ + transpileOnly: true, + compilerOptions: { + module: 'CommonJS', + }, + }); + + require('../src'); +} diff --git a/packages/docgen/package.json b/packages/docgen/package.json new file mode 100644 index 0000000000..0d25860e8e --- /dev/null +++ b/packages/docgen/package.json @@ -0,0 +1,49 @@ +{ + "name": "docgen", + "description": "Tool for generating API Documentation for itself", + "version": "0.1.1-alpha.13", + "private": true, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/spotify/backstage", + "directory": "packages/docgen" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "main": "src/index.ts", + "scripts": { + "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 --" + }, + "bin": { + "backstage-docgen": "bin/backstage-docgen" + }, + "dependencies": { + "chalk": "^4.0.0", + "commander": "^4.1.1", + "fs-extra": "^9.0.0", + "typescript": "^3.9.3", + "ts-node": "^8.6.2" + }, + "devDependencies": { + "@types/fs-extra": "^9.0.1", + "@types/node": "^13.7.2", + "nodemon": "^2.0.2" + }, + "files": [ + "bin", + "dist" + ], + "nodemonConfig": { + "watch": "./src", + "exec": "bin/backstage-docgen", + "ext": "ts" + } +} From 833d3f4bb431809412aefb83732f3897260caac7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 14 Jul 2020 23:00:30 +0200 Subject: [PATCH 12/78] docgen: fix lint issues --- packages/docgen/src/compiler.ts | 12 ++++++------ packages/docgen/src/docgen/ApiDocGenerator.ts | 14 +++++++------- packages/docgen/src/docgen/MarkdownPrinter.ts | 7 +++---- packages/docgen/src/docgen/TypeLocator.ts | 12 ++++++------ .../docgen/src/docgen/TypescriptHighlighter.ts | 4 ++-- packages/docgen/src/docgen/sortSelector.ts | 7 +++---- 6 files changed, 27 insertions(+), 29 deletions(-) diff --git a/packages/docgen/src/compiler.ts b/packages/docgen/src/compiler.ts index 3a196ca709..61837ad1f8 100644 --- a/packages/docgen/src/compiler.ts +++ b/packages/docgen/src/compiler.ts @@ -17,28 +17,28 @@ import * as ts from 'typescript'; import { resolve, join, dirname } from 'path'; import { promisify } from 'util'; -import fs from 'fs'; +import fs from 'fs-extra'; import ApiDocGenerator from './docgen/ApiDocGenerator'; import sortSelector from './docgen/sortSelector'; import TypeLocator from './docgen/TypeLocator'; import ApiDocPrinter from './docgen/ApiDocPrinter'; import TypescriptHighlighter from './docgen/TypescriptHighlighter'; import MarkdownPrinter from './docgen/MarkdownPrinter'; -import { sync as mkdirpSync } from 'mkdirp'; const writeFile = promisify(fs.writeFile); function loadOptions(path: string): ts.CompilerOptions { - let { extends: parent, compilerOptions }: any = require(path); + const config: any = require(path); + let parent = config.extends as string | undefined; if (!parent) { - return compilerOptions; + return config.compilerOptions; } if (parent.startsWith('.')) { parent = join(dirname(path), parent); } - return { ...loadOptions(parent), ...compilerOptions }; + return { ...loadOptions(parent), ...config.compilerOptions }; } async function main() { @@ -84,7 +84,7 @@ async function main() { () => new MarkdownPrinter(new TypescriptHighlighter()), ); - mkdirpSync(resolve(apiRefsDir, 'docs')); + fs.ensureDirSync(resolve(apiRefsDir, 'docs')); await Promise.all( apiDocs.map(apiDoc => { diff --git a/packages/docgen/src/docgen/ApiDocGenerator.ts b/packages/docgen/src/docgen/ApiDocGenerator.ts index e1d1e0f5f1..1142f1155e 100644 --- a/packages/docgen/src/docgen/ApiDocGenerator.ts +++ b/packages/docgen/src/docgen/ApiDocGenerator.ts @@ -116,18 +116,18 @@ export default class ApiDocGenerator { objectLiteral: ts.ObjectLiteralExpression, propertyName: string, ): string { - const prop = objectLiteral.properties.filter( + const matchingProp = objectLiteral.properties.filter( prop => ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propertyName, )[0] as ts.PropertyAssignment; - if (!prop) { + if (!matchingProp) { throw new Error(`no identifier found for property ${propertyName}`); } - const { initializer } = prop; + const { initializer } = matchingProp; if (!ts.isStringLiteral(initializer)) { throw new Error(`no string literal for ${propertyName}`); } @@ -238,22 +238,22 @@ export default class ApiDocGenerator { type: ts.Type | undefined, ): undefined | { symbol: ts.Symbol; declaration: ts.Declaration } { if (!type) { - return; + return undefined; } const symbol = type.aliasSymbol || type.symbol; if (!symbol) { - return; + return undefined; } const [declaration] = symbol.declarations; // Don't generate standalone infos for type paramters if (ts.isTypeParameterDeclaration(declaration)) { - return; + return undefined; } if (!declaration.getSourceFile().fileName.startsWith(this.sourcePath)) { - return; + return undefined; } return { symbol, declaration }; diff --git a/packages/docgen/src/docgen/MarkdownPrinter.ts b/packages/docgen/src/docgen/MarkdownPrinter.ts index c33ecc76a8..36e7c8b1d6 100644 --- a/packages/docgen/src/docgen/MarkdownPrinter.ts +++ b/packages/docgen/src/docgen/MarkdownPrinter.ts @@ -108,7 +108,7 @@ export default class MarkdownPrinter { const parts: Array<{ text: string; path?: string }> = []; - const end = sortedLinks.reduce((prev, link) => { + const endLocation = sortedLinks.reduce((prev, link) => { const [start, end] = link.location; parts.push( { text: text.slice(prev, start) }, @@ -117,15 +117,14 @@ export default class MarkdownPrinter { return end; }, 0); - parts.push({ text: text.slice(end) }); + parts.push({ text: text.slice(endLocation) }); return parts .map(part => { if (part.path) { return `${this.escapeText(part.text)}`; - } else { - return this.highlighter.highlight(this.escapeText(part.text)); } + return this.highlighter.highlight(this.escapeText(part.text)); }) .join(''); } diff --git a/packages/docgen/src/docgen/TypeLocator.ts b/packages/docgen/src/docgen/TypeLocator.ts index 8342548e01..b960640559 100644 --- a/packages/docgen/src/docgen/TypeLocator.ts +++ b/packages/docgen/src/docgen/TypeLocator.ts @@ -95,30 +95,30 @@ export default class TypeLocator { | { constructorType: ts.Type; initializer: ts.NewExpression; name: string } | undefined { if (!ts.isVariableStatement(node)) { - return; + return undefined; } if ( !node.modifiers || !node.modifiers.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword) ) { - return; + return undefined; } const { declarations } = node.declarationList; if (declarations.length !== 1) { - return; + return undefined; } const [declaration] = declarations; const { initializer, name } = declaration; if (!initializer || !name) { - return; + return undefined; } if (!ts.isNewExpression(initializer)) { - return; + return undefined; } if (!ts.isIdentifier(name)) { - return; + return undefined; } const constructorType = this.checker.getTypeAtLocation( diff --git a/packages/docgen/src/docgen/TypescriptHighlighter.ts b/packages/docgen/src/docgen/TypescriptHighlighter.ts index a16c9a2c28..4bbb6548dd 100644 --- a/packages/docgen/src/docgen/TypescriptHighlighter.ts +++ b/packages/docgen/src/docgen/TypescriptHighlighter.ts @@ -56,7 +56,7 @@ export default class TypescriptHighlighter implements Highlighter { ], // keywords ] as readonly [RegExp, string][]; - highlight(text: string): string { + highlight(fullText: string): string { // Each part is either plain text that can be highlighted or text that is already highlighted type HighlightPart = { text: string; highlighted?: boolean }; @@ -80,7 +80,7 @@ export default class TypescriptHighlighter implements Highlighter { // Order here is important, e.g. comments must be first to avoid string literals inside comments being highlighted return TypescriptHighlighter.highlighters .reduce((parts, highlighter) => flatMap(parts, painter(...highlighter)), [ - { text }, + { text: fullText }, ]) .map(({ text }) => text) .join(''); diff --git a/packages/docgen/src/docgen/sortSelector.ts b/packages/docgen/src/docgen/sortSelector.ts index 1ba7a7918d..7590ae39ec 100644 --- a/packages/docgen/src/docgen/sortSelector.ts +++ b/packages/docgen/src/docgen/sortSelector.ts @@ -21,14 +21,13 @@ export default function sortSelector( selector: (x: T) => any, ): (a: T, b: T) => -1 | 1 | 0 { return (a: T, b: T) => { - let aV = selector(a); - let bV = selector(b); + const aV = selector(a); + const bV = selector(b); if (aV < bV) { return -1; } else if (aV > bV) { return 1; - } else { - return 0; } + return 0; }; } From 5a05761afec68a10470dca2f091868ea213ac959 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 15 Jul 2020 11:43:41 +0200 Subject: [PATCH 13/78] docgen: only use type name as link text --- packages/docgen/src/docgen/ApiDocGenerator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docgen/src/docgen/ApiDocGenerator.ts b/packages/docgen/src/docgen/ApiDocGenerator.ts index 1142f1155e..7aaf1463da 100644 --- a/packages/docgen/src/docgen/ApiDocGenerator.ts +++ b/packages/docgen/src/docgen/ApiDocGenerator.ts @@ -225,7 +225,7 @@ export default class ApiDocGenerator { id: typeInfo.id, path: typeInfo.path, name: typeInfo.name, - location: [node.getStart(), node.getEnd()] as const, + location: [node.typeName.getStart(), node.typeName.getEnd()] as const, }; return { From ab450c3ad71dd4ff423c3c76ccb453b2a5076ee7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 15 Jul 2020 11:45:38 +0200 Subject: [PATCH 14/78] docgen: update paths and add filtering based on root paths --- packages/docgen/src/compiler.ts | 19 ++++++++++--------- packages/docgen/src/docgen/TypeLocator.ts | 5 +++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/docgen/src/compiler.ts b/packages/docgen/src/compiler.ts index 61837ad1f8..74ba1db974 100644 --- a/packages/docgen/src/compiler.ts +++ b/packages/docgen/src/compiler.ts @@ -43,14 +43,14 @@ function loadOptions(path: string): ts.CompilerOptions { async function main() { const rootDir = resolve(__dirname, '..'); - const srcDir = resolve(rootDir, 'src'); - const entrypoint = resolve(srcDir, 'index.js'); - const apiRefsDir = resolve(rootDir, 'docgen', 'build', 'api-refs'); + const srcDir = resolve(rootDir, '..', 'core-api', 'src'); + const entrypoint = resolve(srcDir, 'index.ts'); + const apiRefsDir = resolve(rootDir, 'dist'); const mkdocsYaml = resolve(apiRefsDir, 'mkdocs.yml'); process.chdir(rootDir); - const options = loadOptions('../tsconfig.json'); + const options = loadOptions('../../../tsconfig.json'); delete options.moduleResolution; options.removeComments = false; @@ -60,11 +60,12 @@ async function main() { const typeLocator = TypeLocator.fromProgram(program); - const { apis } = typeLocator.findExportedInstances({ - apis: typeLocator.getExportedType( - resolve(srcDir, 'core', 'api', 'ApiRef.ts'), - ), - }); + const { apis } = typeLocator.findExportedInstances( + { + apis: typeLocator.getExportedType(entrypoint, 'createApiRef'), + }, + [srcDir], + ); const apiDocGenerator = ApiDocGenerator.fromProgram(program, rootDir, srcDir); diff --git a/packages/docgen/src/docgen/TypeLocator.ts b/packages/docgen/src/docgen/TypeLocator.ts index b960640559..038a01d35e 100644 --- a/packages/docgen/src/docgen/TypeLocator.ts +++ b/packages/docgen/src/docgen/TypeLocator.ts @@ -54,6 +54,7 @@ export default class TypeLocator { findExportedInstances( typeLookupTable: { [key in T]: ts.Type }, + roots: string[], ): { [key in T]: ExportedInstance[] } { const docMap = new Map(); for (const type of Object.values(typeLookupTable)) { @@ -61,6 +62,10 @@ export default class TypeLocator { } this.program.getSourceFiles().forEach(source => { + const inRoot = roots.some(root => source.fileName.startsWith(root)); + if (!inRoot) { + return; + } ts.forEachChild(source, node => { const decl = this.getExportedConstructorDeclaration(node); if (!decl || !docMap.has(decl.constructorType)) { From e692991226615446037b03cadb6c61c96a73815e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 15 Jul 2020 11:47:30 +0200 Subject: [PATCH 15/78] docgen: add support for intersection types --- packages/docgen/src/docgen/ApiDocGenerator.ts | 14 +++++++++----- packages/docgen/src/docgen/ApiDocPrinter.ts | 4 ++-- packages/docgen/src/docgen/types.ts | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/docgen/src/docgen/ApiDocGenerator.ts b/packages/docgen/src/docgen/ApiDocGenerator.ts index 7aaf1463da..8d0fd04240 100644 --- a/packages/docgen/src/docgen/ApiDocGenerator.ts +++ b/packages/docgen/src/docgen/ApiDocGenerator.ts @@ -61,12 +61,16 @@ export default class ApiDocGenerator { const id = this.getObjectPropertyLiteral(info, 'id'); const description = this.getObjectPropertyLiteral(info, 'description'); - const interfaceInfo = this.getInterfaceInfo(typeArgs[0]); - if (!interfaceInfo) { - throw new Error('api has no info type argument'); - } - return { id, name, source, description, interfaceInfo }; + const rootTypeNode = typeArgs[0]; + const typeNodes = ts.isIntersectionTypeNode(rootTypeNode) + ? rootTypeNode.types.slice() + : [rootTypeNode]; + const interfaceInfos = typeNodes.map(typeNode => + this.getInterfaceInfo(typeNode), + ); + + return { id, name, source, description, interfaceInfos }; } private getNodeDocs(node: ts.Node): string[] { diff --git a/packages/docgen/src/docgen/ApiDocPrinter.ts b/packages/docgen/src/docgen/ApiDocPrinter.ts index 5824047ef7..5047885fc2 100644 --- a/packages/docgen/src/docgen/ApiDocPrinter.ts +++ b/packages/docgen/src/docgen/ApiDocPrinter.ts @@ -47,9 +47,9 @@ export default class ApiDocPrinter { // Remove line numbers from codeblocks printer.style('.linenodiv{ display: none }'); - printer.header(1, `shared/apis/${apiDoc.id}`); + printer.header(1, apiDoc.id); - const ifInfo = apiDoc.interfaceInfo; + const ifInfo = apiDoc.interfaceInfos[0]; if (ifInfo.docs.length) { for (const doc of ifInfo.docs) { diff --git a/packages/docgen/src/docgen/types.ts b/packages/docgen/src/docgen/types.ts index df9a5b93a0..b630caeda0 100644 --- a/packages/docgen/src/docgen/types.ts +++ b/packages/docgen/src/docgen/types.ts @@ -77,7 +77,7 @@ export type ApiDoc = { name: string; source: ts.SourceFile; description: string; - interfaceInfo: InterfaceInfo; + interfaceInfos: InterfaceInfo[]; }; /** From 7a5f4bcbd41c1b85feffdeb2d97b1d0c5b7e1633 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 15 Jul 2020 11:48:01 +0200 Subject: [PATCH 16/78] docgen: support both constructor and call expressions --- packages/docgen/src/docgen/TypeLocator.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/docgen/src/docgen/TypeLocator.ts b/packages/docgen/src/docgen/TypeLocator.ts index 038a01d35e..d5e7025f09 100644 --- a/packages/docgen/src/docgen/TypeLocator.ts +++ b/packages/docgen/src/docgen/TypeLocator.ts @@ -97,7 +97,11 @@ export default class TypeLocator { private getExportedConstructorDeclaration( node: ts.Node, ): - | { constructorType: ts.Type; initializer: ts.NewExpression; name: string } + | { + constructorType: ts.Type; + initializer: ts.CallExpression | ts.NewExpression; + name: string; + } | undefined { if (!ts.isVariableStatement(node)) { return undefined; @@ -119,7 +123,7 @@ export default class TypeLocator { if (!initializer || !name) { return undefined; } - if (!ts.isNewExpression(initializer)) { + if (!ts.isCallOrNewExpression(initializer)) { return undefined; } if (!ts.isIdentifier(name)) { From 2ec31c52642a81754bfa3cc11fb3b6486f5313a9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 15 Jul 2020 12:08:32 +0200 Subject: [PATCH 17/78] docgen: remove flatMap util --- packages/docgen/src/docgen/ApiDocGenerator.ts | 32 +++++++++---------- .../src/docgen/TypescriptHighlighter.ts | 3 +- packages/docgen/src/docgen/flatMap.test.ts | 28 ---------------- packages/docgen/src/docgen/flatMap.ts | 20 ------------ 4 files changed, 16 insertions(+), 67 deletions(-) delete mode 100644 packages/docgen/src/docgen/flatMap.test.ts delete mode 100644 packages/docgen/src/docgen/flatMap.ts diff --git a/packages/docgen/src/docgen/ApiDocGenerator.ts b/packages/docgen/src/docgen/ApiDocGenerator.ts index 8d0fd04240..01b1e0bf0f 100644 --- a/packages/docgen/src/docgen/ApiDocGenerator.ts +++ b/packages/docgen/src/docgen/ApiDocGenerator.ts @@ -24,7 +24,6 @@ import { TypeInfo, TypeLink, } from './types'; -import { flatMap } from './flatMap'; /** * The ApiDocGenerator uses the typescript compiler API to build the data structure that @@ -103,14 +102,13 @@ export default class ApiDocGenerator { ); const docs = this.getNodeDocs(declaration); - const membersAndTypes = flatMap( - Array.from(interfaceMembers.values()), - fieldSymbol => this.getMemberInfo(name, fieldSymbol), - ); + const membersAndTypes = Array.from( + interfaceMembers.values(), + ).flatMap(fieldSymbol => this.getMemberInfo(name, fieldSymbol)); const members = membersAndTypes.map(t => t.member); const dependentTypes = this.flattenTypes( - flatMap(membersAndTypes, t => t.dependentTypes), + membersAndTypes.flatMap(t => t.dependentTypes), ); return { name, docs, file, lineInFile: line + 1, members, dependentTypes }; @@ -184,14 +182,14 @@ export default class ApiDocGenerator { // It doesn't exclude repeated types, e.g. Array>, since we're exiting on duplicate nodes, not types. visited.add(node); - const children = flatMap(node.getChildren(), child => - this.findAllTypeReferences(child, visited), - ); + const children = node + .getChildren() + .flatMap(child => this.findAllTypeReferences(child, visited)); if (!ts.isTypeReferenceNode(node)) { return { - links: flatMap(children, child => child.links), - infos: flatMap(children, child => child.infos), + links: children.flatMap(child => child.links), + infos: children.flatMap(child => child.infos), }; } @@ -200,8 +198,8 @@ export default class ApiDocGenerator { const info = this.validateTypeDeclaration(type); if (!info) { return { - links: flatMap(children, child => child.links), - infos: flatMap(children, child => child.infos), + links: children.flatMap(child => child.links), + infos: children.flatMap(child => child.infos), }; } const { symbol, declaration } = info; @@ -233,8 +231,8 @@ export default class ApiDocGenerator { }; return { - links: [link, ...flatMap(children, child => child.links)], - infos: [typeInfo, ...flatMap(children, child => child.infos)], + links: [link, ...children.flatMap(child => child.links)], + infos: [typeInfo, ...children.flatMap(child => child.infos)], }; }; @@ -270,11 +268,11 @@ export default class ApiDocGenerator { ...parent, children: [], }, - ...flatMap(parent.children, getChildren), + ...parent.children.flatMap(getChildren), ]; } - const flatDescs = flatMap(descs, getChildren); + const flatDescs = descs.flatMap(getChildren); const seenTypes = new Set(); return flatDescs.filter(desc => { if (seenTypes.has(desc.id)) { diff --git a/packages/docgen/src/docgen/TypescriptHighlighter.ts b/packages/docgen/src/docgen/TypescriptHighlighter.ts index 4bbb6548dd..ffdd4f1906 100644 --- a/packages/docgen/src/docgen/TypescriptHighlighter.ts +++ b/packages/docgen/src/docgen/TypescriptHighlighter.ts @@ -15,7 +15,6 @@ */ import { Highlighter } from './types'; -import { flatMap } from './flatMap'; // Simple syntax highlighter that mimics hilite export default class TypescriptHighlighter implements Highlighter { @@ -79,7 +78,7 @@ export default class TypescriptHighlighter implements Highlighter { // Order here is important, e.g. comments must be first to avoid string literals inside comments being highlighted return TypescriptHighlighter.highlighters - .reduce((parts, highlighter) => flatMap(parts, painter(...highlighter)), [ + .reduce((parts, highlighter) => parts.flatMap(painter(...highlighter)), [ { text: fullText }, ]) .map(({ text }) => text) diff --git a/packages/docgen/src/docgen/flatMap.test.ts b/packages/docgen/src/docgen/flatMap.test.ts deleted file mode 100644 index 6186863039..0000000000 --- a/packages/docgen/src/docgen/flatMap.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { flatMap } from './flatMap'; - -describe('flatMap', () => { - it('should flat map', () => { - expect(flatMap([], x => x)).toEqual([]); - expect(flatMap([[]], x => x)).toEqual([]); - // This simple implementation only flattens once - expect(flatMap([[[]]], x => x)).toEqual([[]]); - - expect(flatMap([[1, 2], 3, [4, 5]], x => x)).toEqual([1, 2, 3, 4, 5]); - }); -}); diff --git a/packages/docgen/src/docgen/flatMap.ts b/packages/docgen/src/docgen/flatMap.ts deleted file mode 100644 index bdea672a41..0000000000 --- a/packages/docgen/src/docgen/flatMap.ts +++ /dev/null @@ -1,20 +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. - */ - -export function flatMap(array: T[], func: (t: T) => R[] | R): R[] { - const out = array.map(func); - return ([] as R[]).concat.apply([], out as R[]); -} From f668388c9e5268297300abc054f22395c158b20e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 Jul 2020 11:43:06 +0200 Subject: [PATCH 18/78] docgen: restructure docs into index + type docs --- packages/docgen/src/compiler.ts | 27 ++++-- packages/docgen/src/docgen/ApiDocPrinter.ts | 96 +++++++++++++++++++-- 2 files changed, 107 insertions(+), 16 deletions(-) diff --git a/packages/docgen/src/compiler.ts b/packages/docgen/src/compiler.ts index 74ba1db974..ebb370f550 100644 --- a/packages/docgen/src/compiler.ts +++ b/packages/docgen/src/compiler.ts @@ -68,7 +68,6 @@ async function main() { ); const apiDocGenerator = ApiDocGenerator.fromProgram(program, rootDir, srcDir); - const apiDocs = apis .map(api => { try { @@ -79,7 +78,13 @@ async function main() { ); } }) - .sort(sortSelector(x => x.id)); + .sort(sortSelector(x => x.name)); + + const apiTypes = Object.values( + Object.fromEntries( + apiDocs.flatMap(d => d.interfaceInfos).map(i => [i.name, i]), + ), + ).sort(sortSelector(i => i.name)); const apiDocPrinter = new ApiDocPrinter( () => new MarkdownPrinter(new TypescriptHighlighter()), @@ -87,11 +92,16 @@ async function main() { fs.ensureDirSync(resolve(apiRefsDir, 'docs')); - await Promise.all( - apiDocs.map(apiDoc => { - const data = apiDocPrinter.print(apiDoc); + await writeFile( + join(apiRefsDir, 'docs', 'README.md'), + apiDocPrinter.printApiIndex(apiDocs), + ); - return writeFile(join(apiRefsDir, 'docs', `${apiDoc.name}.md`), data); + await Promise.all( + Object.values(apiTypes).map(apiType => { + const data = apiDocPrinter.printInterface(apiType, apiDocs); + + return writeFile(join(apiRefsDir, 'docs', `${apiType.name}.md`), data); }), ); @@ -100,7 +110,10 @@ async function main() { [ 'site_name: api-references', 'nav:', - ...apiDocs.map(({ id, name }) => ` - ${id}: '${name}.md'`), + ` - Utility API Index: 'README.md'`, + ...apiTypes.map(({ name }) => ` - ${name}: '${name}.md'`), + 'plugins:', + ' - techdocs-core', ].join('\n'), 'utf8', ); diff --git a/packages/docgen/src/docgen/ApiDocPrinter.ts b/packages/docgen/src/docgen/ApiDocPrinter.ts index 5047885fc2..a97885430a 100644 --- a/packages/docgen/src/docgen/ApiDocPrinter.ts +++ b/packages/docgen/src/docgen/ApiDocPrinter.ts @@ -17,7 +17,7 @@ import { execSync } from 'child_process'; import MarkdownPrinter from './MarkdownPrinter'; import sortSelector from './sortSelector'; -import { ApiDoc } from './types'; +import { ApiDoc, InterfaceInfo } from './types'; const GH_BASE_URL = 'https://github.com/spotify/backstage'; @@ -67,7 +67,88 @@ export default class ApiDocPrinter { ); printer.paragraph('All members of the interface are listed below.'); - for (const member of ifInfo.members) { + this.addInterfaceMembers(printer, ifInfo); + + if (ifInfo.dependentTypes.length) { + printer.header(2, 'Types'); + + this.addInterfaceTypes(printer, ifInfo); + } + + return printer.toBuffer(); + } + + printApiIndex(apiDocs: ApiDoc[]): Buffer { + const printer = this.printerFactory(); + + printer.header(1, 'Backstage Utility APIs'); + + for (const api of apiDocs) { + printer.header(3, `${api.name.replace(/ApiRef$/, '')}`, api.id); + + printer.paragraph(api.description); + + const typeLinks = api.interfaceInfos.map(i => `[${i.name}](${i.name})`); + printer.paragraph( + `Implemented type${typeLinks.length > 1 ? 's' : ''}: ${typeLinks.join( + ', ', + )}`, + ); + + printer.paragraph(`ApiRef: ${api.name}`); + } + + return printer.toBuffer(); + } + + printInterface(apiType: InterfaceInfo, apiDocs: ApiDoc[]): Buffer { + const printer = this.printerFactory(); + + // Remove line numbers from codeblocks + printer.style('.linenodiv{ display: none }'); + + printer.header(1, apiType.name); + + printer.paragraph( + `The ${apiType.name} type is defined at ${this.mkTypeLink(apiType)}.`, + ); + + const apiLinks = apiDocs + .filter(ad => ad.interfaceInfos.some(i => i.name === apiType.name)) + .map(ad => `[${ad.name}](../#${ad.id})`); + + if (apiLinks.length === 1) { + printer.paragraph( + `The following Utility API implements this type: ${apiLinks}`, + ); + } else { + printer.paragraph(`The following Utility APIs implement this type:`); + for (const link of apiLinks) { + printer.text(` - ${link}`); + } + } + + printer.header(2, 'Members'); + + this.addInterfaceMembers(printer, apiType); + + if (apiType.dependentTypes.length) { + printer.header(2, 'Supporting types'); + printer.paragraph( + 'These types are part of the API declaration, but may not be unique to this API.', + ); + + this.addInterfaceTypes(printer, apiType); + } + + return printer.toBuffer(); + } + + private addInterfaceMembers( + printer: MarkdownPrinter, + apiType: InterfaceInfo, + ) { + for (const member of apiType.members) { printer.header( 3, `${member.name}${member.type === 'method' ? '()' : ''}`, @@ -80,11 +161,10 @@ export default class ApiDocPrinter { printer.codeWithLinks(member); } + } - if (ifInfo.dependentTypes.length) { - printer.header(2, 'Types'); - } - for (const type of ifInfo.dependentTypes + private addInterfaceTypes(printer: MarkdownPrinter, apiType: InterfaceInfo) { + for (const type of apiType.dependentTypes .slice() .sort(sortSelector(x => x.name))) { printer.header(3, `${type.name}`, type.path); @@ -96,7 +176,7 @@ export default class ApiDocPrinter { printer.paragraph(`Defined at ${this.mkTypeLink(type)}.`); - const usageLinks = [...ifInfo.members, ...ifInfo.dependentTypes] + const usageLinks = [...apiType.members, ...apiType.dependentTypes] .filter(member => { return member.links.some(link => link.id === type.id); }) @@ -106,7 +186,5 @@ export default class ApiDocPrinter { printer.paragraph(`Referenced by: ${usageLinks.join(', ')}.`); } } - - return printer.toBuffer(); } } From b1558c5af7363508e97b54d586816bb67eff742a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 Jul 2020 15:00:00 +0200 Subject: [PATCH 19/78] docgen: add proper cli entrypoint end improve path handling --- packages/docgen/src/docgen/ApiDocGenerator.ts | 13 +--- packages/docgen/src/docgen/ApiDocPrinter.ts | 4 +- packages/docgen/src/docgen/TypeLocator.ts | 19 ++--- .../docgen/src/{compiler.ts => generate.ts} | 78 ++++++------------- packages/docgen/src/index.ts | 44 ++++++++++- 5 files changed, 83 insertions(+), 75 deletions(-) rename packages/docgen/src/{compiler.ts => generate.ts} (53%) diff --git a/packages/docgen/src/docgen/ApiDocGenerator.ts b/packages/docgen/src/docgen/ApiDocGenerator.ts index 01b1e0bf0f..291c3f5b83 100644 --- a/packages/docgen/src/docgen/ApiDocGenerator.ts +++ b/packages/docgen/src/docgen/ApiDocGenerator.ts @@ -36,17 +36,12 @@ import { * While traversing, it collects information such as names, location in source, docs, etc. */ export default class ApiDocGenerator { - static fromProgram( - program: ts.Program, - basePath: string, - sourcePath: string, - ) { - return new ApiDocGenerator(program.getTypeChecker(), basePath, sourcePath); + static fromProgram(program: ts.Program, sourcePath: string) { + return new ApiDocGenerator(program.getTypeChecker(), sourcePath); } constructor( private readonly checker: ts.TypeChecker, - private readonly basePath: string, private readonly sourcePath: string, ) {} @@ -96,7 +91,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 = relative(this.sourcePath, sourceFile.fileName); const { line } = sourceFile.getLineAndCharacterOfPosition( declaration.getStart(), ); @@ -210,7 +205,7 @@ export default class ApiDocGenerator { const { line } = sourceFile.getLineAndCharacterOfPosition( declaration.getStart(), ); - const file = relative(this.basePath, sourceFile.fileName); + const file = relative(this.sourcePath, sourceFile.fileName); const typeInfo = { id: (symbol as any).id, name: symbol.name, diff --git a/packages/docgen/src/docgen/ApiDocPrinter.ts b/packages/docgen/src/docgen/ApiDocPrinter.ts index a97885430a..43107a34ec 100644 --- a/packages/docgen/src/docgen/ApiDocPrinter.ts +++ b/packages/docgen/src/docgen/ApiDocPrinter.ts @@ -19,7 +19,9 @@ import MarkdownPrinter from './MarkdownPrinter'; import sortSelector from './sortSelector'; import { ApiDoc, InterfaceInfo } from './types'; +// TODO(Rugvip): provide through options? const GH_BASE_URL = 'https://github.com/spotify/backstage'; +const SRC_PATH = 'packages/core-api/src'; const COMMIT_SHA = process.env.COMMIT_SHA || execSync('git rev-parse HEAD').toString('utf8'); @@ -37,7 +39,7 @@ export default class ApiDocPrinter { mkTypeLink({ file, lineInFile }: { file: string; lineInFile: number }) { const text = `${file}:${lineInFile}`; - const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${file}#L${lineInFile}`; + const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${SRC_PATH}/${file}#L${lineInFile}`; return `[${text}](${href}){:target="_blank"}`; } diff --git a/packages/docgen/src/docgen/TypeLocator.ts b/packages/docgen/src/docgen/TypeLocator.ts index d5e7025f09..164275b13f 100644 --- a/packages/docgen/src/docgen/TypeLocator.ts +++ b/packages/docgen/src/docgen/TypeLocator.ts @@ -24,17 +24,15 @@ import { ExportedInstance } from './types'; * This is used to e.g. find exported APIs that we should generate documentation for. */ export default class TypeLocator { - private readonly checker: ts.TypeChecker; - private readonly program: ts.Program; - - static fromProgram(program: ts.Program) { - return new TypeLocator(program.getTypeChecker(), program); + static fromProgram(program: ts.Program, sourcePath: string) { + return new TypeLocator(program.getTypeChecker(), program, sourcePath); } - constructor(checker: ts.TypeChecker, program: ts.Program) { - this.checker = checker; - this.program = program; - } + constructor( + private readonly checker: ts.TypeChecker, + private readonly program: ts.Program, + private readonly sourcePath: string, + ) {} getExportedType(path: string, exportedName: string = 'default'): ts.Type { const source = this.program.getSourceFile(path); @@ -54,7 +52,6 @@ export default class TypeLocator { findExportedInstances( typeLookupTable: { [key in T]: ts.Type }, - roots: string[], ): { [key in T]: ExportedInstance[] } { const docMap = new Map(); for (const type of Object.values(typeLookupTable)) { @@ -62,7 +59,7 @@ export default class TypeLocator { } this.program.getSourceFiles().forEach(source => { - const inRoot = roots.some(root => source.fileName.startsWith(root)); + const inRoot = source.fileName.startsWith(this.sourcePath); if (!inRoot) { return; } diff --git a/packages/docgen/src/compiler.ts b/packages/docgen/src/generate.ts similarity index 53% rename from packages/docgen/src/compiler.ts rename to packages/docgen/src/generate.ts index ebb370f550..59ac45b3ad 100644 --- a/packages/docgen/src/compiler.ts +++ b/packages/docgen/src/generate.ts @@ -15,9 +15,8 @@ */ import * as ts from 'typescript'; -import { resolve, join, dirname } from 'path'; -import { promisify } from 'util'; import fs from 'fs-extra'; +import { resolve as resolvePath, join as joinPath } from 'path'; import ApiDocGenerator from './docgen/ApiDocGenerator'; import sortSelector from './docgen/sortSelector'; import TypeLocator from './docgen/TypeLocator'; @@ -25,49 +24,29 @@ import ApiDocPrinter from './docgen/ApiDocPrinter'; import TypescriptHighlighter from './docgen/TypescriptHighlighter'; import MarkdownPrinter from './docgen/MarkdownPrinter'; -const writeFile = promisify(fs.writeFile); +export async function generate(targetPath: string) { + const rootDir = resolvePath(__dirname, '..'); + const srcDir = resolvePath(rootDir, '..', 'core-api', 'src'); + const targetDir = resolvePath(targetPath); + const docsDir = resolvePath(targetDir, 'docs'); -function loadOptions(path: string): ts.CompilerOptions { - const config: any = require(path); - let parent = config.extends as string | undefined; - - if (!parent) { - return config.compilerOptions; - } - if (parent.startsWith('.')) { - parent = join(dirname(path), parent); - } - - return { ...loadOptions(parent), ...config.compilerOptions }; -} - -async function main() { - const rootDir = resolve(__dirname, '..'); - const srcDir = resolve(rootDir, '..', 'core-api', 'src'); - const entrypoint = resolve(srcDir, 'index.ts'); - const apiRefsDir = resolve(rootDir, 'dist'); - const mkdocsYaml = resolve(apiRefsDir, 'mkdocs.yml'); - - process.chdir(rootDir); - - const options = loadOptions('../../../tsconfig.json'); + const options = await fs.readJson(resolvePath('../cli/config/tsconfig.json')); delete options.moduleResolution; - options.removeComments = false; options.noEmit = true; - const program = ts.createProgram([entrypoint], options); + const program = ts.createProgram([resolvePath(srcDir, 'index.ts')], options); - const typeLocator = TypeLocator.fromProgram(program); + const typeLocator = TypeLocator.fromProgram(program, srcDir); - const { apis } = typeLocator.findExportedInstances( - { - apis: typeLocator.getExportedType(entrypoint, 'createApiRef'), - }, - [srcDir], - ); + const { apis } = typeLocator.findExportedInstances({ + apis: typeLocator.getExportedType( + resolvePath(srcDir, 'index.ts'), + 'createApiRef', + ), + }); - const apiDocGenerator = ApiDocGenerator.fromProgram(program, rootDir, srcDir); + const apiDocGenerator = ApiDocGenerator.fromProgram(program, srcDir); const apiDocs = apis .map(api => { try { @@ -90,23 +69,21 @@ async function main() { () => new MarkdownPrinter(new TypescriptHighlighter()), ); - fs.ensureDirSync(resolve(apiRefsDir, 'docs')); + fs.ensureDirSync(docsDir); - await writeFile( - join(apiRefsDir, 'docs', 'README.md'), + await fs.writeFile( + joinPath(docsDir, 'README.md'), apiDocPrinter.printApiIndex(apiDocs), ); - await Promise.all( - Object.values(apiTypes).map(apiType => { - const data = apiDocPrinter.printInterface(apiType, apiDocs); + for (const apiType of Object.values(apiTypes)) { + const data = apiDocPrinter.printInterface(apiType, apiDocs); - return writeFile(join(apiRefsDir, 'docs', `${apiType.name}.md`), data); - }), - ); + await fs.writeFile(joinPath(docsDir, `${apiType.name}.md`), data); + } - fs.writeFileSync( - mkdocsYaml, + await fs.writeFile( + resolvePath(targetDir, 'mkdocs.yml'), [ 'site_name: api-references', 'nav:', @@ -118,8 +95,3 @@ async function main() { 'utf8', ); } - -main().catch(error => { - console.error(error.stack || error); - process.exit(1); -}); diff --git a/packages/docgen/src/index.ts b/packages/docgen/src/index.ts index d57c49f9f8..69f4ec31ad 100644 --- a/packages/docgen/src/index.ts +++ b/packages/docgen/src/index.ts @@ -14,4 +14,46 @@ * limitations under the License. */ -import './compiler'; +import program from 'commander'; +import { resolve as resolvePath } from 'path'; +import chalk from 'chalk'; +import fs from 'fs-extra'; +import { generate } from './generate'; + +const main = (argv: string[]) => { + const pkgJson = fs.readJsonSync(resolvePath(__dirname, '../package.json')); + program.name('docgen').version(pkgJson.version); + + program + .command('generate') + .description( + 'Generate documentation for the declarations in the core-api package', + ) + .option('--output ', 'Output directory [./dist]') + .action(async cmd => { + await generate(cmd.output ?? './dist'); + }); + + program.on('command:*', () => { + console.log(); + console.log( + chalk.red(`Invalid command: ${chalk.cyan(program.args.join(' '))}`), + ); + console.log(chalk.red('See --help for a list of available commands.')); + console.log(); + process.exit(1); + }); + + if (!process.argv.slice(2).length) { + program.outputHelp(chalk.yellow); + } + + program.parse(argv); +}; + +process.on('unhandledRejection', rejection => { + console.error(String(rejection)); + process.exit(1); +}); + +main(process.argv); From c5b2fdd84655be724c3fe082bf6bcd9d7060f16f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 Jul 2020 15:07:45 +0200 Subject: [PATCH 20/78] docgen: read location and generate links for ApiRefs --- packages/docgen/src/docgen/ApiDocGenerator.ts | 13 ++++++++++++- packages/docgen/src/docgen/ApiDocPrinter.ts | 17 ++++++++++------- packages/docgen/src/docgen/types.ts | 3 ++- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/docgen/src/docgen/ApiDocGenerator.ts b/packages/docgen/src/docgen/ApiDocGenerator.ts index 291c3f5b83..7e39a3c57d 100644 --- a/packages/docgen/src/docgen/ApiDocGenerator.ts +++ b/packages/docgen/src/docgen/ApiDocGenerator.ts @@ -55,6 +55,10 @@ export default class ApiDocGenerator { const id = this.getObjectPropertyLiteral(info, 'id'); const description = this.getObjectPropertyLiteral(info, 'description'); + const file = relative(this.sourcePath, source.fileName); + const { line } = source.getLineAndCharacterOfPosition( + apiInstance.node.getStart(), + ); const rootTypeNode = typeArgs[0]; const typeNodes = ts.isIntersectionTypeNode(rootTypeNode) @@ -64,7 +68,14 @@ export default class ApiDocGenerator { this.getInterfaceInfo(typeNode), ); - return { id, name, source, description, interfaceInfos }; + return { + id, + name, + file, + lineInFile: line + 1, + description, + interfaceInfos, + }; } private getNodeDocs(node: ts.Node): string[] { diff --git a/packages/docgen/src/docgen/ApiDocPrinter.ts b/packages/docgen/src/docgen/ApiDocPrinter.ts index 43107a34ec..93c6f8677e 100644 --- a/packages/docgen/src/docgen/ApiDocPrinter.ts +++ b/packages/docgen/src/docgen/ApiDocPrinter.ts @@ -37,10 +37,13 @@ export default class ApiDocPrinter { this.printerFactory = printerFactory; } - mkTypeLink({ file, lineInFile }: { file: string; lineInFile: number }) { - const text = `${file}:${lineInFile}`; + mkLink( + { file, lineInFile }: { file: string; lineInFile: number }, + text?: string, + ) { + const linkText = text ?? `${file}:${lineInFile}`; const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${SRC_PATH}/${file}#L${lineInFile}`; - return `[${text}](${href}){:target="_blank"}`; + return `[${linkText}](${href}){:target="_blank"}`; } print(apiDoc: ApiDoc): Buffer { @@ -63,7 +66,7 @@ export default class ApiDocPrinter { printer.header(2, 'API Interface'); printer.paragraph( - `The API interface type is defined at ${this.mkTypeLink(ifInfo)} as ${ + `The API interface type is defined at ${this.mkLink(ifInfo)} as ${ ifInfo.name }.`, ); @@ -97,7 +100,7 @@ export default class ApiDocPrinter { )}`, ); - printer.paragraph(`ApiRef: ${api.name}`); + printer.paragraph(`ApiRef: ${this.mkLink(api, api.name)}`); } return printer.toBuffer(); @@ -112,7 +115,7 @@ export default class ApiDocPrinter { printer.header(1, apiType.name); printer.paragraph( - `The ${apiType.name} type is defined at ${this.mkTypeLink(apiType)}.`, + `The ${apiType.name} type is defined at ${this.mkLink(apiType)}.`, ); const apiLinks = apiDocs @@ -176,7 +179,7 @@ export default class ApiDocPrinter { } printer.codeWithLinks(type); - printer.paragraph(`Defined at ${this.mkTypeLink(type)}.`); + printer.paragraph(`Defined at ${this.mkLink(type)}.`); const usageLinks = [...apiType.members, ...apiType.dependentTypes] .filter(member => { diff --git a/packages/docgen/src/docgen/types.ts b/packages/docgen/src/docgen/types.ts index b630caeda0..7260b8697f 100644 --- a/packages/docgen/src/docgen/types.ts +++ b/packages/docgen/src/docgen/types.ts @@ -75,8 +75,9 @@ export type InterfaceInfo = { export type ApiDoc = { id: string; name: string; - source: ts.SourceFile; description: string; + file: string; + lineInFile: number; interfaceInfos: InterfaceInfo[]; }; From 2776f735d7cf79b1fe69a8c00b25d68be8464096 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 Jul 2020 15:35:48 +0200 Subject: [PATCH 21/78] docgen: more explanation in api index --- packages/docgen/src/docgen/ApiDocPrinter.ts | 7 +++++++ packages/docgen/src/docgen/MarkdownPrinter.ts | 3 ++- packages/docgen/src/generate.ts | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/docgen/src/docgen/ApiDocPrinter.ts b/packages/docgen/src/docgen/ApiDocPrinter.ts index 93c6f8677e..221d50985e 100644 --- a/packages/docgen/src/docgen/ApiDocPrinter.ts +++ b/packages/docgen/src/docgen/ApiDocPrinter.ts @@ -88,6 +88,13 @@ export default class ApiDocPrinter { printer.header(1, 'Backstage Utility APIs'); + printer.paragraph( + 'The following is a list of all Utility APIs defined by `@backstage/core`.', + 'They are available to use by plugins and components, and need to be provided by the app', + 'They can be accessed using the `useApi` hook, also provided by `@backstage/core`.', + 'For more information, see https://github.com/spotify/backstage/blob/master/docs/api/utility-apis.md.', + ); + for (const api of apiDocs) { printer.header(3, `${api.name.replace(/ApiRef$/, '')}`, api.id); diff --git a/packages/docgen/src/docgen/MarkdownPrinter.ts b/packages/docgen/src/docgen/MarkdownPrinter.ts index 36e7c8b1d6..fb64863da9 100644 --- a/packages/docgen/src/docgen/MarkdownPrinter.ts +++ b/packages/docgen/src/docgen/MarkdownPrinter.ts @@ -48,9 +48,10 @@ export default class MarkdownPrinter { this.line(); } - paragraph(text: string) { + paragraph(...text: string[]) { this.line( text + .join('\n') .trim() .split('\n') .map(line => line.trim()) diff --git a/packages/docgen/src/generate.ts b/packages/docgen/src/generate.ts index 59ac45b3ad..91ea3a02f6 100644 --- a/packages/docgen/src/generate.ts +++ b/packages/docgen/src/generate.ts @@ -87,7 +87,7 @@ export async function generate(targetPath: string) { [ 'site_name: api-references', 'nav:', - ` - Utility API Index: 'README.md'`, + ` - API Index: 'README.md'`, ...apiTypes.map(({ name }) => ` - ${name}: '${name}.md'`), 'plugins:', ' - techdocs-core', From 5078ef4541e258c242d75fc5d38428e3b5948a27 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 Jul 2020 15:45:10 +0200 Subject: [PATCH 22/78] core-api: use type instead of interface to declare ConfigApi --- packages/core-api/src/apis/definitions/ConfigApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-api/src/apis/definitions/ConfigApi.ts b/packages/core-api/src/apis/definitions/ConfigApi.ts index 63a588fc10..1fa6e70d1f 100644 --- a/packages/core-api/src/apis/definitions/ConfigApi.ts +++ b/packages/core-api/src/apis/definitions/ConfigApi.ts @@ -17,7 +17,7 @@ import { createApiRef } from '../ApiRef'; import { Config } from '@backstage/config'; // Using interface to make the ConfigApi name show up in docs -export interface ConfigApi extends Config {} +export type ConfigApi = Config; export const configApiRef = createApiRef({ id: 'core.config', From d8739638495e6484abefa183f45fa8e342d76fb6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 Jul 2020 15:47:02 +0200 Subject: [PATCH 23/78] docgen: use repo root as base path but exclude types in node_modules --- packages/docgen/src/docgen/ApiDocGenerator.ts | 10 +++++----- packages/docgen/src/docgen/ApiDocPrinter.ts | 3 +-- packages/docgen/src/generate.ts | 6 +++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/docgen/src/docgen/ApiDocGenerator.ts b/packages/docgen/src/docgen/ApiDocGenerator.ts index 7e39a3c57d..8e697f42b0 100644 --- a/packages/docgen/src/docgen/ApiDocGenerator.ts +++ b/packages/docgen/src/docgen/ApiDocGenerator.ts @@ -42,7 +42,7 @@ export default class ApiDocGenerator { constructor( private readonly checker: ts.TypeChecker, - private readonly sourcePath: string, + private readonly basePath: string, ) {} toDoc(apiInstance: ExportedInstance): ApiDoc { @@ -55,7 +55,7 @@ export default class ApiDocGenerator { const id = this.getObjectPropertyLiteral(info, 'id'); const description = this.getObjectPropertyLiteral(info, 'description'); - const file = relative(this.sourcePath, source.fileName); + const file = relative(this.basePath, source.fileName); const { line } = source.getLineAndCharacterOfPosition( apiInstance.node.getStart(), ); @@ -102,7 +102,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.sourcePath, sourceFile.fileName); + const file = relative(this.basePath, sourceFile.fileName); const { line } = sourceFile.getLineAndCharacterOfPosition( declaration.getStart(), ); @@ -216,7 +216,7 @@ export default class ApiDocGenerator { const { line } = sourceFile.getLineAndCharacterOfPosition( declaration.getStart(), ); - const file = relative(this.sourcePath, sourceFile.fileName); + const file = relative(this.basePath, sourceFile.fileName); const typeInfo = { id: (symbol as any).id, name: symbol.name, @@ -260,7 +260,7 @@ export default class ApiDocGenerator { return undefined; } - if (!declaration.getSourceFile().fileName.startsWith(this.sourcePath)) { + if (declaration.getSourceFile().fileName.includes('node_modules')) { return undefined; } diff --git a/packages/docgen/src/docgen/ApiDocPrinter.ts b/packages/docgen/src/docgen/ApiDocPrinter.ts index 221d50985e..2cb849e3d2 100644 --- a/packages/docgen/src/docgen/ApiDocPrinter.ts +++ b/packages/docgen/src/docgen/ApiDocPrinter.ts @@ -21,7 +21,6 @@ import { ApiDoc, InterfaceInfo } from './types'; // TODO(Rugvip): provide through options? const GH_BASE_URL = 'https://github.com/spotify/backstage'; -const SRC_PATH = 'packages/core-api/src'; const COMMIT_SHA = process.env.COMMIT_SHA || execSync('git rev-parse HEAD').toString('utf8'); @@ -42,7 +41,7 @@ export default class ApiDocPrinter { text?: string, ) { const linkText = text ?? `${file}:${lineInFile}`; - const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${SRC_PATH}/${file}#L${lineInFile}`; + const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${file}#L${lineInFile}`; return `[${linkText}](${href}){:target="_blank"}`; } diff --git a/packages/docgen/src/generate.ts b/packages/docgen/src/generate.ts index 91ea3a02f6..6cc95c58d2 100644 --- a/packages/docgen/src/generate.ts +++ b/packages/docgen/src/generate.ts @@ -25,8 +25,8 @@ import TypescriptHighlighter from './docgen/TypescriptHighlighter'; import MarkdownPrinter from './docgen/MarkdownPrinter'; export async function generate(targetPath: string) { - const rootDir = resolvePath(__dirname, '..'); - const srcDir = resolvePath(rootDir, '..', 'core-api', 'src'); + const rootDir = resolvePath(__dirname, '../../..'); + const srcDir = resolvePath(rootDir, 'packages', 'core-api', 'src'); const targetDir = resolvePath(targetPath); const docsDir = resolvePath(targetDir, 'docs'); @@ -46,7 +46,7 @@ export async function generate(targetPath: string) { ), }); - const apiDocGenerator = ApiDocGenerator.fromProgram(program, srcDir); + const apiDocGenerator = ApiDocGenerator.fromProgram(program, rootDir); const apiDocs = apis .map(api => { try { From 4cd6774b029f5639f1062f89952de92dcdb73928 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 Jul 2020 15:48:17 +0200 Subject: [PATCH 24/78] docgen: updated title --- packages/docgen/src/docgen/ApiDocPrinter.ts | 2 +- packages/docgen/src/generate.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/docgen/src/docgen/ApiDocPrinter.ts b/packages/docgen/src/docgen/ApiDocPrinter.ts index 2cb849e3d2..cd401e41b4 100644 --- a/packages/docgen/src/docgen/ApiDocPrinter.ts +++ b/packages/docgen/src/docgen/ApiDocPrinter.ts @@ -85,7 +85,7 @@ export default class ApiDocPrinter { printApiIndex(apiDocs: ApiDoc[]): Buffer { const printer = this.printerFactory(); - printer.header(1, 'Backstage Utility APIs'); + printer.header(1, 'Backstage Core Utility APIs'); printer.paragraph( 'The following is a list of all Utility APIs defined by `@backstage/core`.', diff --git a/packages/docgen/src/generate.ts b/packages/docgen/src/generate.ts index 6cc95c58d2..3d9c1ba282 100644 --- a/packages/docgen/src/generate.ts +++ b/packages/docgen/src/generate.ts @@ -85,7 +85,7 @@ export async function generate(targetPath: string) { await fs.writeFile( resolvePath(targetDir, 'mkdocs.yml'), [ - 'site_name: api-references', + 'site_name: Backstage Core Utility API References', 'nav:', ` - API Index: 'README.md'`, ...apiTypes.map(({ name }) => ` - ${name}: '${name}.md'`), From 38b356f8268f8dd795565ff35e62113ed943deca Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 Jul 2020 16:59:14 +0200 Subject: [PATCH 25/78] docgen: updated tests --- .../docgen/src/docgen/ApiDocGenerator.test.ts | 42 +++++++++++-------- .../docgen/src/docgen/TypeLocator.test.ts | 4 +- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/docgen/src/docgen/ApiDocGenerator.test.ts b/packages/docgen/src/docgen/ApiDocGenerator.test.ts index f30c67288d..947ce7ca04 100644 --- a/packages/docgen/src/docgen/ApiDocGenerator.test.ts +++ b/packages/docgen/src/docgen/ApiDocGenerator.test.ts @@ -39,7 +39,7 @@ describe('ApiDocGenerator', () => { }, ); - const typeLocator = TypeLocator.fromProgram(program); + const typeLocator = TypeLocator.fromProgram(program, '/'); const { apiInstances } = typeLocator.findExportedInstances({ apiInstances: typeLocator.getExportedType('/mem/type.ts'), @@ -48,19 +48,24 @@ describe('ApiDocGenerator', () => { expect(apiInstances.length).toBe(1); const [apiInstance] = apiInstances; - const docGenerator = ApiDocGenerator.fromProgram(program, '/mem', '/mem'); + const docGenerator = ApiDocGenerator.fromProgram(program, '/'); const doc = docGenerator.toDoc(apiInstance); expect(doc.id).toBe('my-id'); expect(doc.description).toBe('my-description'); expect(doc.name).toBe('myApi'); - expect(doc.source.fileName).toBe('/mem/index.ts'); - expect(doc.interfaceInfo.dependentTypes).toEqual([]); - expect(doc.interfaceInfo.docs).toEqual([]); - expect(doc.interfaceInfo.file).toBe('index.ts'); - expect(doc.interfaceInfo.lineInFile).toBe(4); - expect(doc.interfaceInfo.members).toEqual([]); - expect(doc.interfaceInfo.name).toBe('MyApiType'); + expect(doc.file).toBe('mem/index.ts'); + expect(doc.lineInFile).toBe(6); + expect(doc.interfaceInfos).toEqual([ + { + dependentTypes: [], + docs: [], + file: 'mem/index.ts', + lineInFile: 4, + members: [], + name: 'MyApiType', + }, + ]); }); it('should generate API docs', () => { @@ -132,7 +137,7 @@ describe('ApiDocGenerator', () => { return ids; }, {} as { [key in string]: number }); - const typeLocator = TypeLocator.fromProgram(program); + const typeLocator = TypeLocator.fromProgram(program, '/mem'); const { apiInstances } = typeLocator.findExportedInstances({ apiInstances: typeLocator.getExportedType('/mem/type.ts'), @@ -141,18 +146,19 @@ describe('ApiDocGenerator', () => { expect(apiInstances.length).toBe(1); const [apiInstance] = apiInstances; - const docGenerator = ApiDocGenerator.fromProgram(program, '/mem', '/mem'); + const docGenerator = ApiDocGenerator.fromProgram(program, '/mem'); const doc = docGenerator.toDoc(apiInstance); expect(doc.id).toBe('my-id'); expect(doc.description).toBe('my-description'); expect(doc.name).toBe('myApi'); - expect(doc.source.fileName).toBe('/mem/index.ts'); - expect(doc.interfaceInfo.docs).toEqual(['MyApiType Docs']); - expect(doc.interfaceInfo.file).toBe('index.ts'); - expect(doc.interfaceInfo.lineInFile).toBe(24); - expect(doc.interfaceInfo.name).toBe('MyApiType'); - expect(doc.interfaceInfo.members).toEqual([ + expect(doc.file).toBe('index.ts'); + expect(doc.interfaceInfos.length).toBe(1); + expect(doc.interfaceInfos[0].docs).toEqual(['MyApiType Docs']); + expect(doc.interfaceInfos[0].file).toBe('index.ts'); + expect(doc.interfaceInfos[0].lineInFile).toBe(24); + expect(doc.interfaceInfos[0].name).toBe('MyApiType'); + expect(doc.interfaceInfos[0].members).toEqual([ { type: 'prop', name: 'x', @@ -199,7 +205,7 @@ describe('ApiDocGenerator', () => { ], }, ]); - expect(doc.interfaceInfo.dependentTypes).toEqual([ + expect(doc.interfaceInfos[0].dependentTypes).toEqual([ { id: Ids.MySubType, name: 'MySubType', diff --git a/packages/docgen/src/docgen/TypeLocator.test.ts b/packages/docgen/src/docgen/TypeLocator.test.ts index 651fbf13b2..5a264fd41c 100644 --- a/packages/docgen/src/docgen/TypeLocator.test.ts +++ b/packages/docgen/src/docgen/TypeLocator.test.ts @@ -22,7 +22,7 @@ describe('TypeLocator', () => { it('should find a default export', () => { const program = createMemProgram('export default class MyApi {}'); - const typeLocator = TypeLocator.fromProgram(program); + const typeLocator = TypeLocator.fromProgram(program, '/mem'); const apiType = typeLocator.getExportedType('/mem/index.ts'); expect(apiType.symbol.name).toBe('default'); @@ -46,7 +46,7 @@ describe('TypeLocator', () => { }, ); - const typeLocator = TypeLocator.fromProgram(program); + const typeLocator = TypeLocator.fromProgram(program, '/mem'); const { apiInstances } = typeLocator.findExportedInstances({ apiInstances: typeLocator.getExportedType('/mem/type.ts'), From 9bbd1fed1871e85967f1fa98ba55074b3997881f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 17 Jul 2020 00:22:16 +0200 Subject: [PATCH 26/78] docgen: add option to output github markdown --- .../{ApiDocPrinter.ts => ApiDocsPrinter.ts} | 84 ++--------- .../src/docgen/GitHubMarkdownPrinter.ts | 140 ++++++++++++++++++ ...nPrinter.ts => TechdocsMarkdownPrinter.ts} | 43 ++++-- packages/docgen/src/docgen/types.ts | 16 ++ packages/docgen/src/generate.ts | 85 +++++++---- packages/docgen/src/index.ts | 6 +- 6 files changed, 266 insertions(+), 108 deletions(-) rename packages/docgen/src/docgen/{ApiDocPrinter.ts => ApiDocsPrinter.ts} (64%) create mode 100644 packages/docgen/src/docgen/GitHubMarkdownPrinter.ts rename packages/docgen/src/docgen/{MarkdownPrinter.ts => TechdocsMarkdownPrinter.ts} (74%) diff --git a/packages/docgen/src/docgen/ApiDocPrinter.ts b/packages/docgen/src/docgen/ApiDocsPrinter.ts similarity index 64% rename from packages/docgen/src/docgen/ApiDocPrinter.ts rename to packages/docgen/src/docgen/ApiDocsPrinter.ts index cd401e41b4..d6c412d498 100644 --- a/packages/docgen/src/docgen/ApiDocPrinter.ts +++ b/packages/docgen/src/docgen/ApiDocsPrinter.ts @@ -14,16 +14,8 @@ * limitations under the License. */ -import { execSync } from 'child_process'; -import MarkdownPrinter from './MarkdownPrinter'; import sortSelector from './sortSelector'; -import { ApiDoc, InterfaceInfo } from './types'; - -// TODO(Rugvip): provide through options? -const GH_BASE_URL = 'https://github.com/spotify/backstage'; - -const COMMIT_SHA = - process.env.COMMIT_SHA || execSync('git rev-parse HEAD').toString('utf8'); +import { ApiDoc, InterfaceInfo, MarkdownPrinter } from './types'; /** * The ApiDocPrinter takes a ApiDoc data structure, typically generated by an ApiDocGenerator, @@ -36,52 +28,6 @@ export default class ApiDocPrinter { this.printerFactory = printerFactory; } - mkLink( - { file, lineInFile }: { file: string; lineInFile: number }, - text?: string, - ) { - const linkText = text ?? `${file}:${lineInFile}`; - const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${file}#L${lineInFile}`; - return `[${linkText}](${href}){:target="_blank"}`; - } - - print(apiDoc: ApiDoc): Buffer { - const printer = this.printerFactory(); - - // Remove line numbers from codeblocks - printer.style('.linenodiv{ display: none }'); - - printer.header(1, apiDoc.id); - - const ifInfo = apiDoc.interfaceInfos[0]; - - if (ifInfo.docs.length) { - for (const doc of ifInfo.docs) { - printer.text(doc); - } - } else { - printer.paragraph(apiDoc.description); - } - - printer.header(2, 'API Interface'); - printer.paragraph( - `The API interface type is defined at ${this.mkLink(ifInfo)} as ${ - ifInfo.name - }.`, - ); - printer.paragraph('All members of the interface are listed below.'); - - this.addInterfaceMembers(printer, ifInfo); - - if (ifInfo.dependentTypes.length) { - printer.header(2, 'Types'); - - this.addInterfaceTypes(printer, ifInfo); - } - - return printer.toBuffer(); - } - printApiIndex(apiDocs: ApiDoc[]): Buffer { const printer = this.printerFactory(); @@ -89,8 +35,8 @@ export default class ApiDocPrinter { printer.paragraph( 'The following is a list of all Utility APIs defined by `@backstage/core`.', - 'They are available to use by plugins and components, and need to be provided by the app', - 'They can be accessed using the `useApi` hook, also provided by `@backstage/core`.', + 'They are available to use by plugins and components, and can be accessed ', + 'using the `useApi` hook, also provided by `@backstage/core`.', 'For more information, see https://github.com/spotify/backstage/blob/master/docs/api/utility-apis.md.', ); @@ -99,14 +45,14 @@ export default class ApiDocPrinter { printer.paragraph(api.description); - const typeLinks = api.interfaceInfos.map(i => `[${i.name}](${i.name})`); + const typeLinks = api.interfaceInfos.map(i => `[${i.name}](./${i.name})`); printer.paragraph( `Implemented type${typeLinks.length > 1 ? 's' : ''}: ${typeLinks.join( ', ', )}`, ); - printer.paragraph(`ApiRef: ${this.mkLink(api, api.name)}`); + printer.paragraph(`ApiRef: ${printer.srcLink(api, api.name)}`); } return printer.toBuffer(); @@ -115,18 +61,18 @@ export default class ApiDocPrinter { printInterface(apiType: InterfaceInfo, apiDocs: ApiDoc[]): Buffer { const printer = this.printerFactory(); - // Remove line numbers from codeblocks - printer.style('.linenodiv{ display: none }'); - printer.header(1, apiType.name); printer.paragraph( - `The ${apiType.name} type is defined at ${this.mkLink(apiType)}.`, + `The ${apiType.name} type is defined at ${printer.srcLink(apiType)}.`, ); const apiLinks = apiDocs .filter(ad => ad.interfaceInfos.some(i => i.name === apiType.name)) - .map(ad => `[${ad.name}](../#${ad.id})`); + .map(ad => { + const link = printer.indexLink() + printer.headerLink(ad.name, ad.id); + return `[${ad.name}](${link})`; + }); if (apiLinks.length === 1) { printer.paragraph( @@ -170,7 +116,7 @@ export default class ApiDocPrinter { printer.text(doc); } - printer.codeWithLinks(member); + printer.code(member); } } @@ -183,15 +129,17 @@ export default class ApiDocPrinter { for (const doc of type.docs) { printer.text(doc); } - printer.codeWithLinks(type); + printer.code(type); - printer.paragraph(`Defined at ${this.mkLink(type)}.`); + printer.paragraph(`Defined at ${printer.srcLink(type)}.`); const usageLinks = [...apiType.members, ...apiType.dependentTypes] .filter(member => { return member.links.some(link => link.id === type.id); }) - .map(({ name, path }) => `[${name}](#${path})`); + .map( + ({ name, path }) => `[${name}](${printer.headerLink(name, path)})`, + ); if (usageLinks.length) { printer.paragraph(`Referenced by: ${usageLinks.join(', ')}.`); diff --git a/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts b/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts new file mode 100644 index 0000000000..391f50a01d --- /dev/null +++ b/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts @@ -0,0 +1,140 @@ +/* + * 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 sortSelector from './sortSelector'; +import { MarkdownPrinter, TypeLink } from './types'; +import { execSync } from 'child_process'; + +// TODO(Rugvip): provide through options? +const GH_BASE_URL = 'https://github.com/spotify/backstage'; + +const COMMIT_SHA = + process.env.COMMIT_SHA || + execSync('git rev-parse HEAD').toString('utf8').trim(); + +/** + * The MarkdownPrinter is a helper class for building markdown documents. + */ +export default class GithubMarkdownPrinter implements MarkdownPrinter { + private str: string = ''; + + private line(line: string = '') { + this.str += `${line}\n`; + } + + text(text: string) { + this.line(text); + this.line(); + } + + header(level: number, text: string) { + this.line(`${'#'.repeat(level)} ${text}`); + this.line(); + } + + headerLink(heading: string): string { + const slug = heading.replace(/[^a-z0-9]/gi, '-').toLowerCase(); + return `#${slug}`; + } + + indexLink() { + return './README.md'; + } + + srcLink( + { file, lineInFile }: { file: string; lineInFile: number }, + text?: string, + ): string { + const linkText = text ?? `${file}:${lineInFile}`; + const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${file}#L${lineInFile}`; + return `[${linkText}](${href})`; + } + + paragraph(...text: string[]) { + this.line( + text + .join('\n') + .trim() + .split('\n') + .map(line => line.trim()) + .join('\n'), + ); + this.line(); + } + + code({ text, links = [] }: { text: string; links?: TypeLink[] }) { + this.line('
');
+    this.line(this.formatWithLinks({ text, links }));
+    this.line('
'); + this.line(); + } + + private escapeText: (text: string) => string = (() => { + const escapes: { [char in string]: string } = { + '&': '&', + '<': '<', + '>': '>', + }; + + return (text: string) => text.replace(/[&<>]/g, char => escapes[char]); + })(); + + private formatWithLinks({ + text, + links = [], + }: { + text: string; + links?: TypeLink[]; + }): string { + const sortedLinks = links.slice().sort(sortSelector(x => x.location[0])); + + sortedLinks.reduce((lastEnd, link) => { + if (link.location[0] <= lastEnd) { + throw new Error( + `overlapping link detected for ${link.path}, ${link.location}`, + ); + } + return link.location[1]; + }, -1); + + const parts: Array<{ text: string; link?: boolean }> = []; + + const endLocation = sortedLinks.reduce((prev, link) => { + const [start, end] = link.location; + parts.push( + { text: text.slice(prev, start) }, + { text: text.slice(start, end), link: true }, + ); + return end; + }, 0); + + parts.push({ text: text.slice(endLocation) }); + + return parts + .map(part => { + if (part.link) { + const link = this.headerLink(part.text); + return `${this.escapeText(part.text)}`; + } + return this.escapeText(part.text); + }) + .join(''); + } + + toBuffer(): Buffer { + return Buffer.from(this.str, 'utf8'); + } +} diff --git a/packages/docgen/src/docgen/MarkdownPrinter.ts b/packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts similarity index 74% rename from packages/docgen/src/docgen/MarkdownPrinter.ts rename to packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts index fb64863da9..3609e0526d 100644 --- a/packages/docgen/src/docgen/MarkdownPrinter.ts +++ b/packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts @@ -15,17 +15,27 @@ */ import sortSelector from './sortSelector'; -import { Highlighter, TypeLink } from './types'; +import { Highlighter, MarkdownPrinter, TypeLink } from './types'; +import { execSync } from 'child_process'; + +// TODO(Rugvip): provide through options? +const GH_BASE_URL = 'https://github.com/spotify/backstage'; + +const COMMIT_SHA = + process.env.COMMIT_SHA || execSync('git rev-parse HEAD').toString('utf8'); /** * The MarkdownPrinter is a helper class for building markdown documents. */ -export default class MarkdownPrinter { +export default class TechdocsMarkdownPrinter implements MarkdownPrinter { private str: string = ''; private readonly highlighter: Highlighter; constructor(highlighter: Highlighter) { this.highlighter = highlighter; + + // Remove line numbers from codeblocks + this.style('.linenodiv{ display: none }'); } private line(line: string = '') { @@ -48,6 +58,23 @@ export default class MarkdownPrinter { this.line(); } + headerLink(heading: string, id?: string): string { + return `#${id ?? heading}`; + } + + indexLink() { + return '../'; + } + + srcLink( + { file, lineInFile }: { file: string; lineInFile: number }, + text?: string, + ): string { + const linkText = text ?? `${file}:${lineInFile}`; + const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${file}#L${lineInFile}`; + return `[${linkText}](${href})`; + } + paragraph(...text: string[]) { this.line( text @@ -60,14 +87,7 @@ export default class MarkdownPrinter { this.line(); } - code(syntax: string = '', block: string) { - this.line(`\`\`\`${syntax}`); - this.line(block); - this.line('```'); - this.line(); - } - - codeWithLinks({ text, links = [] }: { text: string; links?: TypeLink[] }) { + code({ text, links = [] }: { text: string; links?: TypeLink[] }) { this.line('
'); this.line( '
', @@ -123,7 +143,8 @@ export default class MarkdownPrinter { return parts .map(part => { if (part.path) { - return `${this.escapeText(part.text)}`; + const link = this.headerLink(part.text, part.path); + return `${this.escapeText(part.text)}`; } return this.highlighter.highlight(this.escapeText(part.text)); }) diff --git a/packages/docgen/src/docgen/types.ts b/packages/docgen/src/docgen/types.ts index 7260b8697f..d091e9673e 100644 --- a/packages/docgen/src/docgen/types.ts +++ b/packages/docgen/src/docgen/types.ts @@ -95,3 +95,19 @@ export type ExportedInstance = { export type Highlighter = { highlight(test: string): string; }; + +export type MarkdownPrinter = { + text(text: string): void; + header(level: number, text: string, id?: string): void; + paragraph(...text: string[]): void; + code(options: { text: string; links?: TypeLink[] }): void; + + headerLink(header: string, id?: string): string; + indexLink(): string; + srcLink( + { file, lineInFile }: { file: string; lineInFile: number }, + text?: string, + ): string; + + toBuffer(): Buffer; +}; diff --git a/packages/docgen/src/generate.ts b/packages/docgen/src/generate.ts index 3d9c1ba282..3587e2776b 100644 --- a/packages/docgen/src/generate.ts +++ b/packages/docgen/src/generate.ts @@ -20,15 +20,26 @@ import { resolve as resolvePath, join as joinPath } from 'path'; import ApiDocGenerator from './docgen/ApiDocGenerator'; import sortSelector from './docgen/sortSelector'; import TypeLocator from './docgen/TypeLocator'; -import ApiDocPrinter from './docgen/ApiDocPrinter'; +import ApiDocPrinter from './docgen/ApiDocsPrinter'; import TypescriptHighlighter from './docgen/TypescriptHighlighter'; -import MarkdownPrinter from './docgen/MarkdownPrinter'; +import GitHubMarkdownPrinter from './docgen/GitHubMarkdownPrinter'; +import TechdocsMarkdownPrinter from './docgen/TechdocsMarkdownPrinter'; + +const FORMATS = ['github', 'techdocs'] as const; + +export async function generate( + targetPath: string, + format: typeof FORMATS[number], +) { + if (!FORMATS.includes(format)) { + throw new TypeError( + `Invalid format, '${format}', must be one of ${FORMATS.join(', ')}`, + ); + } -export async function generate(targetPath: string) { const rootDir = resolvePath(__dirname, '../../..'); const srcDir = resolvePath(rootDir, 'packages', 'core-api', 'src'); const targetDir = resolvePath(targetPath); - const docsDir = resolvePath(targetDir, 'docs'); const options = await fs.readJson(resolvePath('../cli/config/tsconfig.json')); @@ -65,33 +76,51 @@ export async function generate(targetPath: string) { ), ).sort(sortSelector(i => i.name)); - const apiDocPrinter = new ApiDocPrinter( - () => new MarkdownPrinter(new TypescriptHighlighter()), - ); + if (format === 'techdocs') { + const docsDir = resolvePath(targetDir, 'docs'); + await fs.ensureDir(docsDir); - fs.ensureDirSync(docsDir); + const apiDocPrinter = new ApiDocPrinter( + () => new TechdocsMarkdownPrinter(new TypescriptHighlighter()), + ); - await fs.writeFile( - joinPath(docsDir, 'README.md'), - apiDocPrinter.printApiIndex(apiDocs), - ); + await fs.writeFile( + joinPath(docsDir, 'README.md'), + apiDocPrinter.printApiIndex(apiDocs), + ); - for (const apiType of Object.values(apiTypes)) { - const data = apiDocPrinter.printInterface(apiType, apiDocs); + for (const apiType of Object.values(apiTypes)) { + const data = apiDocPrinter.printInterface(apiType, apiDocs); - await fs.writeFile(joinPath(docsDir, `${apiType.name}.md`), data); + await fs.writeFile(joinPath(docsDir, `${apiType.name}.md`), data); + } + + await fs.writeFile( + resolvePath(targetDir, 'mkdocs.yml'), + [ + 'site_name: Backstage Core Utility API References', + 'nav:', + ` - API Index: 'README.md'`, + ...apiTypes.map(({ name }) => ` - ${name}: '${name}.md'`), + 'plugins:', + ' - techdocs-core', + ].join('\n'), + 'utf8', + ); + } else { + await fs.ensureDir(targetDir); + + const apiDocPrinter = new ApiDocPrinter(() => new GitHubMarkdownPrinter()); + + await fs.writeFile( + joinPath(targetDir, 'README.md'), + apiDocPrinter.printApiIndex(apiDocs), + ); + + for (const apiType of Object.values(apiTypes)) { + const data = apiDocPrinter.printInterface(apiType, apiDocs); + + await fs.writeFile(joinPath(targetDir, `${apiType.name}.md`), data); + } } - - await fs.writeFile( - resolvePath(targetDir, 'mkdocs.yml'), - [ - 'site_name: Backstage Core Utility API References', - 'nav:', - ` - API Index: 'README.md'`, - ...apiTypes.map(({ name }) => ` - ${name}: '${name}.md'`), - 'plugins:', - ' - techdocs-core', - ].join('\n'), - 'utf8', - ); } diff --git a/packages/docgen/src/index.ts b/packages/docgen/src/index.ts index 69f4ec31ad..866299151a 100644 --- a/packages/docgen/src/index.ts +++ b/packages/docgen/src/index.ts @@ -30,8 +30,12 @@ const main = (argv: string[]) => { 'Generate documentation for the declarations in the core-api package', ) .option('--output ', 'Output directory [./dist]') + .option( + '--format ', + 'Output format, either techdocs or github [techdocs]', + ) .action(async cmd => { - await generate(cmd.output ?? './dist'); + await generate(cmd.output ?? './dist', cmd.format ?? 'techdocs'); }); program.on('command:*', () => { From 6b6820bdd29109caa0fbaf4c00b258a09c66ae2a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 17 Jul 2020 00:39:33 +0200 Subject: [PATCH 27/78] docgen: more docs --- packages/docgen/src/docgen/ApiDocGenerator.ts | 31 ++++++++++++++++++- packages/docgen/src/docgen/ApiDocsPrinter.ts | 6 ++++ .../src/docgen/GitHubMarkdownPrinter.ts | 2 +- .../src/docgen/TechdocsMarkdownPrinter.ts | 2 +- packages/docgen/src/docgen/TypeLocator.ts | 7 +++++ packages/docgen/src/docgen/types.ts | 3 ++ 6 files changed, 48 insertions(+), 3 deletions(-) diff --git a/packages/docgen/src/docgen/ApiDocGenerator.ts b/packages/docgen/src/docgen/ApiDocGenerator.ts index 8e697f42b0..bfe449155b 100644 --- a/packages/docgen/src/docgen/ApiDocGenerator.ts +++ b/packages/docgen/src/docgen/ApiDocGenerator.ts @@ -30,7 +30,7 @@ import { * describes a Backstage API and all of it's related types. * * It receives an exported instance that of the form - * `export name = new Api({id: ..., description: ...})`. + * `export name = createApiRef({id: ..., description: ...})`. * It will then traverse all fields and methods on the interface type, and also * types and declaration of all types that are used by those, both directly and indirectly. * While traversing, it collects information such as names, location in source, docs, etc. @@ -45,6 +45,9 @@ export default class ApiDocGenerator { private readonly basePath: string, ) {} + /** + * Generate documentation information for a given exported symbol. + */ toDoc(apiInstance: ExportedInstance): ApiDoc { const { name, source, args, typeArgs } = apiInstance; @@ -78,12 +81,18 @@ export default class ApiDocGenerator { }; } + /** + * Grab jsDoc and regular comments for a node + */ private getNodeDocs(node: ts.Node): string[] { const docNodes = ((node && (node as any).jsDoc) || []) as ts.JSDoc[]; const docs = docNodes.map(docNode => docNode.comment || '').filter(Boolean); return docs; } + /** + * Collect information about a top-level type. + */ private getInterfaceInfo(typeNode: ts.TypeNode): InterfaceInfo { if (ts.isTypeQueryNode(typeNode)) { throw new Error( @@ -120,6 +129,9 @@ export default class ApiDocGenerator { return { name, docs, file, lineInFile: line + 1, members, dependentTypes }; } + /** + * Grab primitive values from an object literal expression. + */ private getObjectPropertyLiteral( objectLiteral: ts.ObjectLiteralExpression, propertyName: string, @@ -143,6 +155,9 @@ export default class ApiDocGenerator { return initializer.text; } + /** + * Get field definitions for a given symbol. + */ private getMemberInfo( parentName: string, symbol: ts.Symbol, @@ -177,6 +192,9 @@ export default class ApiDocGenerator { }; } + /** + * Recursively search for references to types that we care about and want to include in the documentation. + */ private findAllTypeReferences = ( node: ts.Node | undefined, visited: Set = new Set(), @@ -242,6 +260,9 @@ export default class ApiDocGenerator { }; }; + /** + * Check if a given type declaration is one that we care about. + */ private validateTypeDeclaration( type: ts.Type | undefined, ): undefined | { symbol: ts.Symbol; declaration: ts.Declaration } { @@ -267,6 +288,10 @@ export default class ApiDocGenerator { return { symbol, declaration }; } + /** + * Flatten a tree of TypeInfos with children into a flat + * list of TypeInfos with duplicates removed. + */ private flattenTypes(descs: TypeInfo[]): TypeInfo[] { function getChildren(parent: TypeInfo): TypeInfo[] { return [ @@ -289,6 +314,10 @@ export default class ApiDocGenerator { }); } + /** + * Calculate link positions within a block of code, with 0 being + * the first character in the block. + */ private recalculateLinkOffsets( parent: ts.Node, links: TypeLink[], diff --git a/packages/docgen/src/docgen/ApiDocsPrinter.ts b/packages/docgen/src/docgen/ApiDocsPrinter.ts index d6c412d498..05fcfbd141 100644 --- a/packages/docgen/src/docgen/ApiDocsPrinter.ts +++ b/packages/docgen/src/docgen/ApiDocsPrinter.ts @@ -28,6 +28,9 @@ export default class ApiDocPrinter { this.printerFactory = printerFactory; } + /** + * Print an index file with all ApiRefs and what types they implement. + */ printApiIndex(apiDocs: ApiDoc[]): Buffer { const printer = this.printerFactory(); @@ -58,6 +61,9 @@ export default class ApiDocPrinter { return printer.toBuffer(); } + /** + * Print documentation page for a type implemented by an ApiRef and + */ printInterface(apiType: InterfaceInfo, apiDocs: ApiDoc[]): Buffer { const printer = this.printerFactory(); diff --git a/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts b/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts index 391f50a01d..eb8e10c102 100644 --- a/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts +++ b/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts @@ -26,7 +26,7 @@ const COMMIT_SHA = execSync('git rev-parse HEAD').toString('utf8').trim(); /** - * The MarkdownPrinter is a helper class for building markdown documents. + * The GithubMarkdownPrinter is a MarkdownPrinter for printing Github-flavored markdown documents. */ export default class GithubMarkdownPrinter implements MarkdownPrinter { private str: string = ''; diff --git a/packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts b/packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts index 3609e0526d..91fde3cd27 100644 --- a/packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts +++ b/packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts @@ -25,7 +25,7 @@ const COMMIT_SHA = process.env.COMMIT_SHA || execSync('git rev-parse HEAD').toString('utf8'); /** - * The MarkdownPrinter is a helper class for building markdown documents. + * The TechdocsMarkdownPrinter is a MarkdownPrinter for building TechDocs markdown documents. */ export default class TechdocsMarkdownPrinter implements MarkdownPrinter { private str: string = ''; diff --git a/packages/docgen/src/docgen/TypeLocator.ts b/packages/docgen/src/docgen/TypeLocator.ts index 164275b13f..6030258112 100644 --- a/packages/docgen/src/docgen/TypeLocator.ts +++ b/packages/docgen/src/docgen/TypeLocator.ts @@ -34,6 +34,9 @@ export default class TypeLocator { private readonly sourcePath: string, ) {} + /** + * Get the type of a symbol by export path and name + */ getExportedType(path: string, exportedName: string = 'default'): ts.Type { const source = this.program.getSourceFile(path); if (!source) { @@ -50,6 +53,10 @@ export default class TypeLocator { return type; } + /** + * Find exported instances and return values from calls using the types + * provided in the lookup table. + */ findExportedInstances( typeLookupTable: { [key in T]: ts.Type }, ): { [key in T]: ExportedInstance[] } { diff --git a/packages/docgen/src/docgen/types.ts b/packages/docgen/src/docgen/types.ts index d091e9673e..fae368b58d 100644 --- a/packages/docgen/src/docgen/types.ts +++ b/packages/docgen/src/docgen/types.ts @@ -96,6 +96,9 @@ export type Highlighter = { highlight(test: string): string; }; +/** + * Markdown printer is an abstraction for printing markdown documents of different flavors. + */ export type MarkdownPrinter = { text(text: string): void; header(level: number, text: string, id?: string): void; From 511ca47c27e826c30d4a340ac03c93423696f64d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 17 Jul 2020 10:08:46 +0200 Subject: [PATCH 28/78] docgen: add docgen command to generate gfm docs --- package.json | 1 + packages/docgen/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9d702c4755..79af25a9af 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "lint": "lerna run lint --since origin/master --", "lint:all": "lerna run lint --", "lint:type-deps": "node scripts/check-type-dependencies.js", + "docgen": "lerna run docgen", "docker-build": "yarn bundle && docker build . -t spotify/backstage", "docker-build:all": "yarn tsc && yarn build && yarn docker-build && yarn workspace example-backend build-image", "create-plugin": "backstage-cli create-plugin", diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 0d25860e8e..eafddbdda3 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -18,7 +18,7 @@ "build": "backstage-cli build --outputs cjs", "lint": "backstage-cli lint", "test": "backstage-cli test", - "test:e2e": "node e2e-test/cli-e2e-test.js", + "docgen": "backstage-docgen generate --output ../../docs/reference/utility-apis --format github", "clean": "backstage-cli clean", "start": "nodemon --" }, From 8cd5fb5e7c40142a0646b9d47c82961fe601f395 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 17 Jul 2020 10:09:12 +0200 Subject: [PATCH 29/78] docs: generate utility api reference --- docs/reference/utility-apis/AlertApi.md | 115 +++++++++ docs/reference/utility-apis/AppThemeApi.md | 225 +++++++++++++++++ .../utility-apis/BackstageIdentityApi.md | 88 +++++++ docs/reference/utility-apis/Config.md | 179 ++++++++++++++ docs/reference/utility-apis/ErrorApi.md | 135 ++++++++++ .../reference/utility-apis/FeatureFlagsApi.md | 33 +++ docs/reference/utility-apis/IdentityApi.md | 81 ++++++ docs/reference/utility-apis/OAuthApi.md | 119 +++++++++ .../reference/utility-apis/OAuthRequestApi.md | 232 ++++++++++++++++++ .../utility-apis/OpenIdConnectApi.md | 75 ++++++ docs/reference/utility-apis/ProfileInfoApi.md | 94 +++++++ docs/reference/utility-apis/README.md | 135 ++++++++++ .../reference/utility-apis/SessionStateApi.md | 115 +++++++++ docs/reference/utility-apis/StorageApi.md | 186 ++++++++++++++ 14 files changed, 1812 insertions(+) create mode 100644 docs/reference/utility-apis/AlertApi.md create mode 100644 docs/reference/utility-apis/AppThemeApi.md create mode 100644 docs/reference/utility-apis/BackstageIdentityApi.md create mode 100644 docs/reference/utility-apis/Config.md create mode 100644 docs/reference/utility-apis/ErrorApi.md create mode 100644 docs/reference/utility-apis/FeatureFlagsApi.md create mode 100644 docs/reference/utility-apis/IdentityApi.md create mode 100644 docs/reference/utility-apis/OAuthApi.md create mode 100644 docs/reference/utility-apis/OAuthRequestApi.md create mode 100644 docs/reference/utility-apis/OpenIdConnectApi.md create mode 100644 docs/reference/utility-apis/ProfileInfoApi.md create mode 100644 docs/reference/utility-apis/README.md create mode 100644 docs/reference/utility-apis/SessionStateApi.md create mode 100644 docs/reference/utility-apis/StorageApi.md diff --git a/docs/reference/utility-apis/AlertApi.md b/docs/reference/utility-apis/AlertApi.md new file mode 100644 index 0000000000..b7d1a4d65d --- /dev/null +++ b/docs/reference/utility-apis/AlertApi.md @@ -0,0 +1,115 @@ +# AlertApi + +The AlertApi type is defined at +[packages/core-api/src/apis/definitions/AlertApi.ts:29](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AlertApi.ts#L29). + +The following Utility API implements this type: +[alertApiRef](./README.md#alertapiref) + +## Members + +### post() + +Post an alert for handling by the application. + +
+post(alert: AlertMessage): void
+
+ +### alert\$() + +Observe alerts posted by other parts of the application. + +
+alert$(): Observable<AlertMessage>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AlertMessage + +
+export type AlertMessage = {
+  message: string;
+  // Severity will default to success since that is what material ui defaults the value to.
+  severity?: 'success' | 'info' | 'warning' | 'error';
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/AlertApi.ts:19](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AlertApi.ts#L19). + +Referenced by: [post](#post), [alert\$](#alert-). + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). + +Referenced by: [alert\$](#alert-). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/AppThemeApi.md b/docs/reference/utility-apis/AppThemeApi.md new file mode 100644 index 0000000000..79d41ef6f6 --- /dev/null +++ b/docs/reference/utility-apis/AppThemeApi.md @@ -0,0 +1,225 @@ +# AppThemeApi + +The AppThemeApi type is defined at +[packages/core-api/src/apis/definitions/AppThemeApi.ts:50](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AppThemeApi.ts#L50). + +The following Utility API implements this type: +[appThemeApiRef](./README.md#appthemeapiref) + +## Members + +### getInstalledThemes() + +Get a list of available themes. + +
+getInstalledThemes(): AppTheme[]
+
+ +### activeThemeId\$() + +Observe the currently selected theme. A value of undefined means no specific +theme has been selected. + +
+activeThemeId$(): Observable<string | undefined>
+
+ +### getActiveThemeId() + +Get the current theme ID. Returns undefined if no specific theme is selected. + +
+getActiveThemeId(): string | undefined
+
+ +### setActiveThemeId() + +Set a specific theme to use in the app, overriding the default theme selection. + +Clear the selection by passing in undefined. + +
+setActiveThemeId(themeId?: string): void
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AppTheme + +Describes a theme provided by the app. + +
+export type AppTheme = {
+  /**
+   * ID used to remember theme selections.
+   */
+  id: string;
+
+  /**
+   * Title of the theme
+   */
+  title: string;
+
+  /**
+   * Theme variant
+   */
+  variant: 'light' | 'dark';
+
+  /**
+   * The specialized MaterialUI theme instance.
+   */
+  theme: BackstageTheme;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/AppThemeApi.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AppThemeApi.ts#L24). + +Referenced by: [getInstalledThemes](#getinstalledthemes). + +### BackstagePalette + +
+export type BackstagePalette = Palette & PaletteAdditions
+
+ +Defined at +[packages/theme/src/types.ts:63](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/theme/src/types.ts#L63). + +Referenced by: [BackstageTheme](#backstagetheme). + +### BackstageTheme + +
+export interface BackstageTheme extends Theme {
+  palette: BackstagePalette;
+}
+
+ +Defined at +[packages/theme/src/types.ts:66](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/theme/src/types.ts#L66). + +Referenced by: [AppTheme](#apptheme). + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). + +Referenced by: [activeThemeId\$](#activethemeid-). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### PaletteAdditions + +
+type PaletteAdditions = {
+  status: {
+    ok: string;
+    warning: string;
+    error: string;
+    pending: string;
+    running: string;
+    aborted: string;
+  };
+  border: string;
+  textContrast: string;
+  textVerySubtle: string;
+  textSubtle: string;
+  highlight: string;
+  errorBackground: string;
+  warningBackground: string;
+  infoBackground: string;
+  errorText: string;
+  infoText: string;
+  warningText: string;
+  linkHover: string;
+  link: string;
+  gold: string;
+  sidebar: string;
+  tabbar: {
+    indicator: string;
+  };
+  bursts: {
+    fontColor: string;
+    slackChannelText: string;
+    backgroundColor: {
+      default: string;
+    };
+  };
+  pinSidebarButton: {
+    icon: string;
+    background: string;
+  };
+}
+
+ +Defined at +[packages/theme/src/types.ts:23](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/theme/src/types.ts#L23). + +Referenced by: [BackstagePalette](#backstagepalette). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/BackstageIdentityApi.md b/docs/reference/utility-apis/BackstageIdentityApi.md new file mode 100644 index 0000000000..717a294621 --- /dev/null +++ b/docs/reference/utility-apis/BackstageIdentityApi.md @@ -0,0 +1,88 @@ +# BackstageIdentityApi + +The BackstageIdentityApi type is defined at +[packages/core-api/src/apis/definitions/auth.ts:144](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L144). + +The following Utility APIs implement this type: + +- [githubAuthApiRef](./README.md#githubauthapiref) + +- [gitlabAuthApiRef](./README.md#gitlabauthapiref) + +- [googleAuthApiRef](./README.md#googleauthapiref) + +- [oktaAuthApiRef](./README.md#oktaauthapiref) + +## Members + +### getBackstageIdentity() + +Get the user's identity within Backstage. This should normally not be called +directly, use the @IdentityApi instead. + +If the optional flag is not set, a session is guaranteed to be returned, while +if the optional flag is set, the session may be undefined. See +@AuthRequestOptions for more details. + +
+getBackstageIdentity(
+    options?: AuthRequestOptions,
+  ): Promise<BackstageIdentity | undefined>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AuthRequestOptions + +
+export type AuthRequestOptions = {
+  /**
+   * If this is set to true, the user will not be prompted to log in,
+   * and an empty response will be returned if there is no existing session.
+   *
+   * This can be used to perform a check whether the user is logged in, or if you don't
+   * want to force a user to be logged in, but provide functionality if they already are.
+   *
+   * @default false
+   */
+  optional?: boolean;
+
+  /**
+   * If this is set to true, the request will bypass the regular oauth login modal
+   * and open the login popup directly.
+   *
+   * The method must be called synchronously from a user action for this to work in all browsers.
+   *
+   * @default false
+   */
+  instantPopup?: boolean;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L40). + +Referenced by: [getBackstageIdentity](#getbackstageidentity). + +### BackstageIdentity + +
+export type BackstageIdentity = {
+  /**
+   * The backstage user ID.
+   */
+  id: string;
+
+  /**
+   * An ID token that can be used to authenticate the user within Backstage.
+   */
+  idToken: string;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:157](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L157). + +Referenced by: [getBackstageIdentity](#getbackstageidentity). diff --git a/docs/reference/utility-apis/Config.md b/docs/reference/utility-apis/Config.md new file mode 100644 index 0000000000..ef53c5adcf --- /dev/null +++ b/docs/reference/utility-apis/Config.md @@ -0,0 +1,179 @@ +# Config + +The Config type is defined at +[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/config/src/types.ts#L32). + +The following Utility API implements this type: +[configApiRef](./README.md#configapiref) + +## Members + +### keys() + +
+keys(): string[]
+
+ +### get() + +
+get(key: string): JsonValue
+
+ +### getOptional() + +
+getOptional(key: string): JsonValue | undefined
+
+ +### getConfig() + +
+getConfig(key: string): Config
+
+ +### getOptionalConfig() + +
+getOptionalConfig(key: string): Config | undefined
+
+ +### getConfigArray() + +
+getConfigArray(key: string): Config[]
+
+ +### getOptionalConfigArray() + +
+getOptionalConfigArray(key: string): Config[] | undefined
+
+ +### getNumber() + +
+getNumber(key: string): number
+
+ +### getOptionalNumber() + +
+getOptionalNumber(key: string): number | undefined
+
+ +### getBoolean() + +
+getBoolean(key: string): boolean
+
+ +### getOptionalBoolean() + +
+getOptionalBoolean(key: string): boolean | undefined
+
+ +### getString() + +
+getString(key: string): string
+
+ +### getOptionalString() + +
+getOptionalString(key: string): string | undefined
+
+ +### getStringArray() + +
+getStringArray(key: string): string[]
+
+ +### getOptionalStringArray() + +
+getOptionalStringArray(key: string): string[] | undefined
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### Config + +
+export type Config = {
+  keys(): string[];
+
+  get(key: string): JsonValue;
+  getOptional(key: string): JsonValue | undefined;
+
+  getConfig(key: string): Config;
+  getOptionalConfig(key: string): Config | undefined;
+
+  getConfigArray(key: string): Config[];
+  getOptionalConfigArray(key: string): Config[] | undefined;
+
+  getNumber(key: string): number;
+  getOptionalNumber(key: string): number | undefined;
+
+  getBoolean(key: string): boolean;
+  getOptionalBoolean(key: string): boolean | undefined;
+
+  getString(key: string): string;
+  getOptionalString(key: string): string | undefined;
+
+  getStringArray(key: string): string[];
+  getOptionalStringArray(key: string): string[] | undefined;
+}
+
+ +Defined at +[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/config/src/types.ts#L32). + +Referenced by: [getConfig](#getconfig), [getOptionalConfig](#getoptionalconfig), +[getConfigArray](#getconfigarray), +[getOptionalConfigArray](#getoptionalconfigarray), [Config](#config). + +### JsonArray + +
+export type JsonArray = JsonValue[]
+
+ +Defined at +[packages/config/src/types.ts:18](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/config/src/types.ts#L18). + +Referenced by: [JsonValue](#jsonvalue). + +### JsonObject + +
+export type JsonObject = { [key in string]?: JsonValue }
+
+ +Defined at +[packages/config/src/types.ts:17](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/config/src/types.ts#L17). + +Referenced by: [JsonValue](#jsonvalue). + +### JsonValue + +
+export type JsonValue =
+  | JsonObject
+  | JsonArray
+  | number
+  | string
+  | boolean
+  | null
+
+ +Defined at +[packages/config/src/types.ts:19](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/config/src/types.ts#L19). + +Referenced by: [get](#get), [getOptional](#getoptional), +[JsonObject](#jsonobject), [JsonArray](#jsonarray), [Config](#config). diff --git a/docs/reference/utility-apis/ErrorApi.md b/docs/reference/utility-apis/ErrorApi.md new file mode 100644 index 0000000000..efd4a3ed00 --- /dev/null +++ b/docs/reference/utility-apis/ErrorApi.md @@ -0,0 +1,135 @@ +# ErrorApi + +The ErrorApi type is defined at +[packages/core-api/src/apis/definitions/ErrorApi.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/ErrorApi.ts#L53). + +The following Utility API implements this type: +[errorApiRef](./README.md#errorapiref) + +## Members + +### post() + +Post an error for handling by the application. + +
+post(error: Error, context?: ErrorContext): void
+
+ +### error\$() + +Observe errors posted by other parts of the application. + +
+error$(): Observable<{ error: Error; context?: ErrorContext }>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### Error + +Mirrors the javascript Error class, for the purpose of providing documentation +and optional fields. + +
+type Error = {
+  name: string;
+  message: string;
+  stack?: string;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/ErrorApi.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/ErrorApi.ts#L24). + +Referenced by: [post](#post), [error\$](#error-). + +### ErrorContext + +Provides additional information about an error that was posted to the +application. + +
+export type ErrorContext = {
+  // If set to true, this error should not be displayed to the user. Defaults to false.
+  hidden?: boolean;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/ErrorApi.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/ErrorApi.ts#L33). + +Referenced by: [post](#post), [error\$](#error-). + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). + +Referenced by: [error\$](#error-). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/FeatureFlagsApi.md b/docs/reference/utility-apis/FeatureFlagsApi.md new file mode 100644 index 0000000000..b3320b404a --- /dev/null +++ b/docs/reference/utility-apis/FeatureFlagsApi.md @@ -0,0 +1,33 @@ +# FeatureFlagsApi + +The FeatureFlagsApi type is defined at +[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:41](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L41). + +The following Utility API implements this type: +[featureFlagsApiRef](./README.md#featureflagsapiref) + +## Members + +### registeredFeatureFlags + +Store a list of registered feature flags. + +
+registeredFeatureFlags: FeatureFlagsRegistryItem[]
+
+ +### getFlags() + +Get a list of all feature flags from the current user. + +
+getFlags(): UserFlags
+
+ +### getRegisteredFlags() + +Get a list of all registered flags. + +
+getRegisteredFlags(): FeatureFlagsRegistry
+
diff --git a/docs/reference/utility-apis/IdentityApi.md b/docs/reference/utility-apis/IdentityApi.md new file mode 100644 index 0000000000..f5de414cf5 --- /dev/null +++ b/docs/reference/utility-apis/IdentityApi.md @@ -0,0 +1,81 @@ +# IdentityApi + +The IdentityApi type is defined at +[packages/core-api/src/apis/definitions/IdentityApi.ts:22](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/IdentityApi.ts#L22). + +The following Utility API implements this type: +[identityApiRef](./README.md#identityapiref) + +## Members + +### getUserId() + +The ID of the signed in user. This ID is not meant to be presented to the user, +but used as an opaque string to pass on to backends or use in frontend logic. + +TODO: The intention of the user ID is to be able to tie the user to an identity +that is known by the catalog and/or identity backend. It should for example be +possible to fetch all owned components using this ID. + +
+getUserId(): string
+
+ +### getProfile() + +The profile of the signed in user. + +
+getProfile(): ProfileInfo
+
+ +### getIdToken() + +An OpenID Connect ID Token which proves the identity of the signed in user. + +The ID token will be undefined if the signed in user does not have a verified +identity, such as a demo user or mocked user for e2e tests. + +
+getIdToken(): Promise<string | undefined>
+
+ +### logout() + +Log out the current user + +
+logout(): Promise<void>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### ProfileInfo + +Profile information of the user. + +
+export type ProfileInfo = {
+  /**
+   * Email ID.
+   */
+  email?: string;
+
+  /**
+   * Display name that can be presented to the user.
+   */
+  displayName?: string;
+
+  /**
+   * URL to an avatar image of the user.
+   */
+  picture?: string;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L172). + +Referenced by: [getProfile](#getprofile). diff --git a/docs/reference/utility-apis/OAuthApi.md b/docs/reference/utility-apis/OAuthApi.md new file mode 100644 index 0000000000..e3334601f1 --- /dev/null +++ b/docs/reference/utility-apis/OAuthApi.md @@ -0,0 +1,119 @@ +# OAuthApi + +The OAuthApi type is defined at +[packages/core-api/src/apis/definitions/auth.ts:67](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L67). + +The following Utility APIs implement this type: + +- [githubAuthApiRef](./README.md#githubauthapiref) + +- [gitlabAuthApiRef](./README.md#gitlabauthapiref) + +- [googleAuthApiRef](./README.md#googleauthapiref) + +- [oauth2ApiRef](./README.md#oauth2apiref) + +- [oktaAuthApiRef](./README.md#oktaauthapiref) + +## Members + +### getAccessToken() + +Requests an OAuth 2 Access Token, optionally with a set of scopes. The access +token allows you to make requests on behalf of the user, and the copes may grant +you broader access, depending on the auth provider. + +Each auth provider has separate handling of scope, so you need to look at the +documentation for each one to know what scope you need to request. + +This method is cheap and should be called each time an access token is used. Do +not for example store the access token in React component state, as that could +cause the token to expire. Instead fetch a new access token for each request. + +Be sure to include all required scopes when requesting an access token. When +testing your implementation it is best to log out the Backstage session and then +visit your plugin page directly, as you might already have some required scopes +in your existing session. Not requesting the correct scopes can lead to 403 or +other authorization errors, which can be tricky to debug. + +If the user has not yet granted access to the provider and the set of requested +scopes, the user will be prompted to log in. The returned promise will not +resolve until the user has successfully logged in. The returned promise can be +rejected, but only if the user rejects the login request. + +
+getAccessToken(
+    scope?: OAuthScope,
+    options?: AuthRequestOptions,
+  ): Promise<string>
+
+ +### logout() + +Log out the user's session. This will reload the page. + +
+logout(): Promise<void>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AuthRequestOptions + +
+export type AuthRequestOptions = {
+  /**
+   * If this is set to true, the user will not be prompted to log in,
+   * and an empty response will be returned if there is no existing session.
+   *
+   * This can be used to perform a check whether the user is logged in, or if you don't
+   * want to force a user to be logged in, but provide functionality if they already are.
+   *
+   * @default false
+   */
+  optional?: boolean;
+
+  /**
+   * If this is set to true, the request will bypass the regular oauth login modal
+   * and open the login popup directly.
+   *
+   * The method must be called synchronously from a user action for this to work in all browsers.
+   *
+   * @default false
+   */
+  instantPopup?: boolean;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L40). + +Referenced by: [getAccessToken](#getaccesstoken). + +### OAuthScope + +This file contains declarations for common interfaces of auth-related APIs. The +declarations should be used to signal which type of authentication and +authorization methods each separate auth provider supports. + +For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, +would be declared as follows: + +const googleAuthApiRef = createApiRef({ ... }) + +An array of scopes, or a scope string formatted according to the auth provider, +which is typically a space separated list. + +See the documentation for each auth provider for the list of scopes supported by +each provider. + +
+export type OAuthScope = string | string[]
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:38](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L38). + +Referenced by: [getAccessToken](#getaccesstoken). diff --git a/docs/reference/utility-apis/OAuthRequestApi.md b/docs/reference/utility-apis/OAuthRequestApi.md new file mode 100644 index 0000000000..895c515fb0 --- /dev/null +++ b/docs/reference/utility-apis/OAuthRequestApi.md @@ -0,0 +1,232 @@ +# OAuthRequestApi + +The OAuthRequestApi type is defined at +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:99](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L99). + +The following Utility API implements this type: +[oauthRequestApiRef](./README.md#oauthrequestapiref) + +## Members + +### createAuthRequester() + +A utility for showing login popups or similar things, and merging together +multiple requests for different scopes into one request that inclues all scopes. + +The passed in options provide information about the login provider, and how to +handle auth requests. + +The returned AuthRequester function is used to request login with new scopes. +These requests are merged together and forwarded to the auth handler, as soon as +a consumer of auth requests triggers an auth flow. + +See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. + +
+createAuthRequester<AuthResponse>(
+    options: AuthRequesterOptions<AuthResponse>,
+  ): AuthRequester<AuthResponse>
+
+ +### authRequest\$() + +Observers panding auth requests. The returned observable will emit all current +active auth request, at most one for each created auth requester. + +Each request has its own info about the login provider, forwarded from the auth +requester options. + +Depending on user interaction, the request should either be rejected, or used to +trigger the auth handler. If the request is rejected, all pending AuthRequester +calls will fail with a "RejectedError". If a auth is triggered, and the auth +handler resolves successfully, then all currently pending AuthRequester calls +will resolve to the value returned by the onAuthRequest call. + +
+authRequest$(): Observable<PendingAuthRequest[]>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AuthProvider + +Information about the auth provider that we're requesting a login towards. + +This should be shown to the user so that they can be informed about what login +is being requested before a popup is shown. + +
+export type AuthProvider = {
+  /**
+   * Title for the auth provider, for example "GitHub"
+   */
+  title: string;
+
+  /**
+   * Icon for the auth provider.
+   */
+  icon: IconComponent;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:27](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L27). + +Referenced by: [AuthRequesterOptions](#authrequesteroptions), +[PendingAuthRequest](#pendingauthrequest). + +### AuthRequester + +Function used to trigger new auth requests for a set of scopes. + +The returned promise will resolve to the same value returned by the +onAuthRequest in the AuthRequesterOptions. Or rejected, if the request is +rejected. + +This function can be called multiple times before the promise resolves. All +calls will be merged into one request, and the scopes forwarded to the +onAuthRequest will be the union of all requested scopes. + +
+export type AuthRequester<AuthResponse> = (
+  scopes: Set<string>,
+) => Promise<AuthResponse>
+
+ +Defined at +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:66](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L66). + +Referenced by: [createAuthRequester](#createauthrequester). + +### AuthRequesterOptions + +Describes how to handle auth requests. Both how to show them to the user, and +what to do when the user accesses the auth request. + +
+export type AuthRequesterOptions<AuthResponse> = {
+  /**
+   * Information about the auth provider, which will be forwarded to auth requests.
+   */
+  provider: AuthProvider;
+
+  /**
+   * Implementation of the auth flow, which will be called synchronously when
+   * trigger() is called on an auth requests.
+   */
+  onAuthRequest(scopes: Set<string>): Promise<AuthResponse>;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:43](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L43). + +Referenced by: [createAuthRequester](#createauthrequester). + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). + +Referenced by: [authRequest\$](#authrequest-). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### PendingAuthRequest + +An pending auth request for a single auth provider. The request will remain in +this pending state until either reject() or trigger() is called. + +Any new requests for the same provider are merged into the existing pending +request, meaning there will only ever be a single pending request for a given +provider. + +
+export type PendingAuthRequest = {
+  /**
+   * Information about the auth provider, as given in the AuthRequesterOptions
+   */
+  provider: AuthProvider;
+
+  /**
+   * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError".
+   */
+  reject: () => void;
+
+  /**
+   * Trigger the auth request to continue the auth flow, by for example showing a popup.
+   *
+   * Synchronously calls onAuthRequest with all scope currently in the request.
+   */
+  trigger(): Promise<void>;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:77](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L77). + +Referenced by: [authRequest\$](#authrequest-). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/OpenIdConnectApi.md b/docs/reference/utility-apis/OpenIdConnectApi.md new file mode 100644 index 0000000000..c626ccf3a1 --- /dev/null +++ b/docs/reference/utility-apis/OpenIdConnectApi.md @@ -0,0 +1,75 @@ +# OpenIdConnectApi + +The OpenIdConnectApi type is defined at +[packages/core-api/src/apis/definitions/auth.ts:104](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L104). + +The following Utility APIs implement this type: + +- [googleAuthApiRef](./README.md#googleauthapiref) + +- [oauth2ApiRef](./README.md#oauth2apiref) + +- [oktaAuthApiRef](./README.md#oktaauthapiref) + +## Members + +### getIdToken() + +Requests an OpenID Connect ID Token. + +This method is cheap and should be called each time an ID token is used. Do not +for example store the id token in React component state, as that could cause the +token to expire. Instead fetch a new id token for each request. + +If the user has not yet logged in to Google inside Backstage, the user will be +prompted to log in. The returned promise will not resolve until the user has +successfully logged in. The returned promise can be rejected, but only if the +user rejects the login request. + +
+getIdToken(options?: AuthRequestOptions): Promise<string>
+
+ +### logout() + +Log out the user's session. This will reload the page. + +
+logout(): Promise<void>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AuthRequestOptions + +
+export type AuthRequestOptions = {
+  /**
+   * If this is set to true, the user will not be prompted to log in,
+   * and an empty response will be returned if there is no existing session.
+   *
+   * This can be used to perform a check whether the user is logged in, or if you don't
+   * want to force a user to be logged in, but provide functionality if they already are.
+   *
+   * @default false
+   */
+  optional?: boolean;
+
+  /**
+   * If this is set to true, the request will bypass the regular oauth login modal
+   * and open the login popup directly.
+   *
+   * The method must be called synchronously from a user action for this to work in all browsers.
+   *
+   * @default false
+   */
+  instantPopup?: boolean;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L40). + +Referenced by: [getIdToken](#getidtoken). diff --git a/docs/reference/utility-apis/ProfileInfoApi.md b/docs/reference/utility-apis/ProfileInfoApi.md new file mode 100644 index 0000000000..6fcbc99950 --- /dev/null +++ b/docs/reference/utility-apis/ProfileInfoApi.md @@ -0,0 +1,94 @@ +# ProfileInfoApi + +The ProfileInfoApi type is defined at +[packages/core-api/src/apis/definitions/auth.ts:127](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L127). + +The following Utility APIs implement this type: + +- [githubAuthApiRef](./README.md#githubauthapiref) + +- [gitlabAuthApiRef](./README.md#gitlabauthapiref) + +- [googleAuthApiRef](./README.md#googleauthapiref) + +- [oauth2ApiRef](./README.md#oauth2apiref) + +- [oktaAuthApiRef](./README.md#oktaauthapiref) + +## Members + +### getProfile() + +Get profile information for the user as supplied by this auth provider. + +If the optional flag is not set, a session is guaranteed to be returned, while +if the optional flag is set, the session may be undefined. See +@AuthRequestOptions for more details. + +
+getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AuthRequestOptions + +
+export type AuthRequestOptions = {
+  /**
+   * If this is set to true, the user will not be prompted to log in,
+   * and an empty response will be returned if there is no existing session.
+   *
+   * This can be used to perform a check whether the user is logged in, or if you don't
+   * want to force a user to be logged in, but provide functionality if they already are.
+   *
+   * @default false
+   */
+  optional?: boolean;
+
+  /**
+   * If this is set to true, the request will bypass the regular oauth login modal
+   * and open the login popup directly.
+   *
+   * The method must be called synchronously from a user action for this to work in all browsers.
+   *
+   * @default false
+   */
+  instantPopup?: boolean;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L40). + +Referenced by: [getProfile](#getprofile). + +### ProfileInfo + +Profile information of the user. + +
+export type ProfileInfo = {
+  /**
+   * Email ID.
+   */
+  email?: string;
+
+  /**
+   * Display name that can be presented to the user.
+   */
+  displayName?: string;
+
+  /**
+   * URL to an avatar image of the user.
+   */
+  picture?: string;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L172). + +Referenced by: [getProfile](#getprofile). diff --git a/docs/reference/utility-apis/README.md b/docs/reference/utility-apis/README.md new file mode 100644 index 0000000000..398d55f605 --- /dev/null +++ b/docs/reference/utility-apis/README.md @@ -0,0 +1,135 @@ +# Backstage Core Utility APIs + +The following is a list of all Utility APIs defined by `@backstage/core`. They +are available to use by plugins and components, and can be accessed using the +`useApi` hook, also provided by `@backstage/core`. For more information, see +https://github.com/spotify/backstage/blob/master/docs/api/utility-apis.md. + +### alert + +Used to report alerts and forward them to the app + +Implemented type: [AlertApi](./AlertApi) + +ApiRef: +[alertApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AlertApi.ts#L41) + +### appTheme + +API Used to configure the app theme, and enumerate options + +Implemented type: [AppThemeApi](./AppThemeApi) + +ApiRef: +[appThemeApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AppThemeApi.ts#L74) + +### config + +Used to access runtime configuration + +Implemented type: [Config](./Config) + +ApiRef: +[configApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/ConfigApi.ts#L22) + +### error + +Used to report errors and forward them to the app + +Implemented type: [ErrorApi](./ErrorApi) + +ApiRef: +[errorApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/ErrorApi.ts#L65) + +### featureFlags + +Used to toggle functionality in features across Backstage + +Implemented type: [FeatureFlagsApi](./FeatureFlagsApi) + +ApiRef: +[featureFlagsApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L58) + +### githubAuth + +Provides authentication towards Github APIs + +Implemented types: [OAuthApi](./OAuthApi), [ProfileInfoApi](./ProfileInfoApi), +[BackstageIdentityApi](./BackstageIdentityApi), +[SessionStateApi](./SessionStateApi) + +ApiRef: +[githubAuthApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L230) + +### gitlabAuth + +Provides authentication towards Gitlab APIs + +Implemented types: [OAuthApi](./OAuthApi), [ProfileInfoApi](./ProfileInfoApi), +[BackstageIdentityApi](./BackstageIdentityApi), +[SessionStateApi](./SessionStateApi) + +ApiRef: +[gitlabAuthApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L260) + +### googleAuth + +Provides authentication towards Google APIs and identities + +Implemented types: [OAuthApi](./OAuthApi), +[OpenIdConnectApi](./OpenIdConnectApi), [ProfileInfoApi](./ProfileInfoApi), +[BackstageIdentityApi](./BackstageIdentityApi), +[SessionStateApi](./SessionStateApi) + +ApiRef: +[googleAuthApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L213) + +### identity + +Provides access to the identity of the signed in user + +Implemented type: [IdentityApi](./IdentityApi) + +ApiRef: +[identityApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/IdentityApi.ts#L54) + +### oauth2 + +Example of how to use oauth2 custom provider + +Implemented types: [OAuthApi](./OAuthApi), +[OpenIdConnectApi](./OpenIdConnectApi), [ProfileInfoApi](./ProfileInfoApi), +[SessionStateApi](./SessionStateApi) + +ApiRef: +[oauth2ApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L270) + +### oauthRequest + +An API for implementing unified OAuth flows in Backstage + +Implemented type: [OAuthRequestApi](./OAuthRequestApi) + +ApiRef: +[oauthRequestApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L130) + +### oktaAuth + +Provides authentication towards Okta APIs + +Implemented types: [OAuthApi](./OAuthApi), +[OpenIdConnectApi](./OpenIdConnectApi), [ProfileInfoApi](./ProfileInfoApi), +[BackstageIdentityApi](./BackstageIdentityApi), +[SessionStateApi](./SessionStateApi) + +ApiRef: +[oktaAuthApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L243) + +### storage + +Provides the ability to store data which is unique to the user + +Implemented type: [StorageApi](./StorageApi) + +ApiRef: +[storageApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/StorageApi.ts#L68) diff --git a/docs/reference/utility-apis/SessionStateApi.md b/docs/reference/utility-apis/SessionStateApi.md new file mode 100644 index 0000000000..d15074aa9d --- /dev/null +++ b/docs/reference/utility-apis/SessionStateApi.md @@ -0,0 +1,115 @@ +# SessionStateApi + +The SessionStateApi type is defined at +[packages/core-api/src/apis/definitions/auth.ts:201](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L201). + +The following Utility APIs implement this type: + +- [githubAuthApiRef](./README.md#githubauthapiref) + +- [gitlabAuthApiRef](./README.md#gitlabauthapiref) + +- [googleAuthApiRef](./README.md#googleauthapiref) + +- [oauth2ApiRef](./README.md#oauth2apiref) + +- [oktaAuthApiRef](./README.md#oktaauthapiref) + +## Members + +### sessionState\$() + +
+sessionState$(): Observable<SessionState>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). + +Referenced by: [sessionState\$](#sessionstate-). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### SessionState + +Session state values passed to subscribers of the SessionStateApi. + +
+export enum SessionState {
+  SignedIn = 'SignedIn',
+  SignedOut = 'SignedOut',
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:192](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L192). + +Referenced by: [sessionState\$](#sessionstate-). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/StorageApi.md b/docs/reference/utility-apis/StorageApi.md new file mode 100644 index 0000000000..dd366bcf18 --- /dev/null +++ b/docs/reference/utility-apis/StorageApi.md @@ -0,0 +1,186 @@ +# StorageApi + +The StorageApi type is defined at +[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/StorageApi.ts#L31). + +The following Utility API implements this type: +[storageApiRef](./README.md#storageapiref) + +## Members + +### forBucket() + +Create a bucket to store data in. + +
+forBucket(name: string): StorageApi
+
+ +### get() + +Get the current value for persistent data, use observe\$ to be notified of +updates. + +
+get<T>(key: string): T | undefined
+
+ +### remove() + +Remove persistent data. + +
+remove(key: string): Promise<void>
+
+ +### set() + +Save persistant data, and emit messages to anyone that is using observe\$ for +this key + +
+set(key: string, data: any): Promise<void>
+
+ +### observe\$() + +Observe changes on a particular key in the bucket + +
+observe$<T>(key: string): Observable<StorageValueChange<T>>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). + +Referenced by: [observe\$](#observe-), [StorageApi](#storageapi). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### StorageApi + +
+export interface StorageApi {
+  /**
+   * Create a bucket to store data in.
+   * @param {String} name Namespace for the storage to be stored under,
+   *                      will inherit previous namespaces too
+   */
+  forBucket(name: string): StorageApi;
+
+  /**
+   * Get the current value for persistent data, use observe$ to be notified of updates.
+   *
+   * @param {String} key Unique key associated with the data.
+   * @return {Object} data The data that should is stored.
+   */
+  get<T>(key: string): T | undefined;
+
+  /**
+   * Remove persistent data.
+   *
+   * @param {String} key Unique key associated with the data.
+   */
+  remove(key: string): Promise<void>;
+
+  /**
+   * Save persistant data, and emit messages to anyone that is using observe$ for this key
+   *
+   * @param {String} key Unique key associated with the data.
+   */
+  set(key: string, data: any): Promise<void>;
+
+  /**
+   * Observe changes on a particular key in the bucket
+   * @param {String} key Unique key associated with the data
+   */
+  observe$<T>(key: string): Observable<StorageValueChange<T>>;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/StorageApi.ts#L31). + +Referenced by: [forBucket](#forbucket). + +### StorageValueChange + +
+export type StorageValueChange<T = any> = {
+  key: string;
+  newValue?: T;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/StorageApi.ts:21](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/StorageApi.ts#L21). + +Referenced by: [observe\$](#observe-), [StorageApi](#storageapi). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). From 3dd7fa7c3e5b3740f5b0fdca20c9065e148948ca Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 17 Jul 2020 10:16:05 +0200 Subject: [PATCH 30/78] docs: add links to api references --- docs/README.md | 3 ++- docs/api/utility-apis.md | 3 ++- docs/auth/index.md | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 904fdf3356..c7721976c1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -76,7 +76,8 @@ better yet, a pull request. - [Figma resources](dls/figma.md) - API references - TypeScript API - - [Utilities](api/utility-apis.md) + - [Utility APIs](api/utility-apis.md) + - [Utility API References](reference/utility-apis/README.md) - [createPlugin](reference/createPlugin.md) - [createPlugin-feature-flags](reference/createPlugin-feature-flags.md) - [createPlugin-router](reference/createPlugin-router.md) diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 2b15da0295..f87db15e7e 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -19,7 +19,8 @@ during their entire life cycle. Each Utility API is tied to an `ApiRef` instance, which is a global singleton object without any additional state or functionality, its only purpose is to reference Utility APIs. `ApiRef`s are create using `createApiRef`, which is -exported by `@backstage/core`. There are many predefined Utility APIs defined in +exported by `@backstage/core`. There are many +[predefined Utility APIs](../reference/utility-apis/README.md) defined in `@backstage/core`, and they're all exported with a name of the pattern `*ApiRef`, for example `errorApiRef`. diff --git a/docs/auth/index.md b/docs/auth/index.md index 0cb56501ab..6f9af17cba 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -26,7 +26,8 @@ OAuth helps in that regard. The method with which frontend plugins request access to third party services is through [Utility APIs](../api/utility-apis.md) for each service provider. For a -full list of providers, see [TODO](#TODO). +full list of providers, see the +[Utility API References](../reference/utility-apis/README.md). ### Identity - WIP From f5e5520cbc57a9b269216660e7d2d19f5507dc90 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 17 Jul 2020 10:17:23 +0200 Subject: [PATCH 31/78] docgen: added README --- packages/docgen/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 packages/docgen/README.md diff --git a/packages/docgen/README.md b/packages/docgen/README.md new file mode 100644 index 0000000000..be1d7cd521 --- /dev/null +++ b/packages/docgen/README.md @@ -0,0 +1,21 @@ +# DocGen - API Reference Documentation Generator + +The docgen package provides a CLI to generate markdown documentation for all exported ApiRefs in `@backstage/core`. The documentation is generated based on exported `ApiRef` instances and their type parameters. + +The CLI supports generating both TechDocs and GitHub Markdown, where the TechDocs one provides some better linking and syntax highlighting. + +## Usage + +To generate markdown documentation in the top-level `docs/` directory, run the following: + +```bash +yarn docgen +``` + +## TODO + +This package was lifted out from the Spotify internal Backstage project and could use some further work: + +- Use a higher-level TypeScript compiler library, e.g. `ts-morph`. +- Support for generating docs for any package or multiple packages. +- Better handling of self-referencing types in APIs, e.g. ConfigApi. From 7e5451d8a9676e4b7fd3b2716a50680e8f83d5cb Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 17 Jul 2020 10:34:34 +0200 Subject: [PATCH 32/78] docgen: fix markdown links --- packages/docgen/src/docgen/ApiDocsPrinter.ts | 14 +++++++++++--- .../docgen/src/docgen/GitHubMarkdownPrinter.ts | 4 ++++ .../docgen/src/docgen/TechdocsMarkdownPrinter.ts | 4 ++++ packages/docgen/src/docgen/types.ts | 3 +++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/docgen/src/docgen/ApiDocsPrinter.ts b/packages/docgen/src/docgen/ApiDocsPrinter.ts index 05fcfbd141..fdd53afc8e 100644 --- a/packages/docgen/src/docgen/ApiDocsPrinter.ts +++ b/packages/docgen/src/docgen/ApiDocsPrinter.ts @@ -44,11 +44,13 @@ export default class ApiDocPrinter { ); for (const api of apiDocs) { - printer.header(3, `${api.name.replace(/ApiRef$/, '')}`, api.id); + printer.header(3, `${this.apiDisplayName(api)}`, api.id); printer.paragraph(api.description); - const typeLinks = api.interfaceInfos.map(i => `[${i.name}](./${i.name})`); + const typeLinks = api.interfaceInfos.map( + i => `[${i.name}](${printer.pageLink(i.name)})`, + ); printer.paragraph( `Implemented type${typeLinks.length > 1 ? 's' : ''}: ${typeLinks.join( ', ', @@ -76,7 +78,9 @@ export default class ApiDocPrinter { const apiLinks = apiDocs .filter(ad => ad.interfaceInfos.some(i => i.name === apiType.name)) .map(ad => { - const link = printer.indexLink() + printer.headerLink(ad.name, ad.id); + const link = + printer.indexLink() + + printer.headerLink(this.apiDisplayName(ad), ad.id); return `[${ad.name}](${link})`; }); @@ -152,4 +156,8 @@ export default class ApiDocPrinter { } } } + + private apiDisplayName(api: ApiDoc): string { + return api.name.replace(/ApiRef$/, ''); + } } diff --git a/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts b/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts index eb8e10c102..fd82e023d3 100644 --- a/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts +++ b/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts @@ -54,6 +54,10 @@ export default class GithubMarkdownPrinter implements MarkdownPrinter { return './README.md'; } + pageLink(name: string) { + return `./${name}.md`; + } + srcLink( { file, lineInFile }: { file: string; lineInFile: number }, text?: string, diff --git a/packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts b/packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts index 91fde3cd27..f2453f314f 100644 --- a/packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts +++ b/packages/docgen/src/docgen/TechdocsMarkdownPrinter.ts @@ -66,6 +66,10 @@ export default class TechdocsMarkdownPrinter implements MarkdownPrinter { return '../'; } + pageLink(name: string) { + return `./${name}/`; + } + srcLink( { file, lineInFile }: { file: string; lineInFile: number }, text?: string, diff --git a/packages/docgen/src/docgen/types.ts b/packages/docgen/src/docgen/types.ts index fae368b58d..7aff54d04c 100644 --- a/packages/docgen/src/docgen/types.ts +++ b/packages/docgen/src/docgen/types.ts @@ -106,7 +106,10 @@ export type MarkdownPrinter = { code(options: { text: string; links?: TypeLink[] }): void; headerLink(header: string, id?: string): string; + /** Link from pages to index */ indexLink(): string; + /** Link from index to pages */ + pageLink(name: string): string; srcLink( { file, lineInFile }: { file: string; lineInFile: number }, text?: string, From 922c7296fd5e02093694a7893ef0d49524ab9d24 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 17 Jul 2020 10:38:29 +0200 Subject: [PATCH 33/78] docgen: use github-slugger to generate better slugs --- packages/docgen/package.json | 6 ++++-- .../docgen/src/docgen/GitHubMarkdownPrinter.ts | 3 ++- yarn.lock | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/docgen/package.json b/packages/docgen/package.json index eafddbdda3..6e73dfeda5 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -29,11 +29,13 @@ "chalk": "^4.0.0", "commander": "^4.1.1", "fs-extra": "^9.0.0", - "typescript": "^3.9.3", - "ts-node": "^8.6.2" + "github-slugger": "^1.3.0", + "ts-node": "^8.6.2", + "typescript": "^3.9.3" }, "devDependencies": { "@types/fs-extra": "^9.0.1", + "@types/github-slugger": "^1.3.0", "@types/node": "^13.7.2", "nodemon": "^2.0.2" }, diff --git a/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts b/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts index fd82e023d3..64d38d8b15 100644 --- a/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts +++ b/packages/docgen/src/docgen/GitHubMarkdownPrinter.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import GithubSlugger from 'github-slugger'; import sortSelector from './sortSelector'; import { MarkdownPrinter, TypeLink } from './types'; import { execSync } from 'child_process'; @@ -46,7 +47,7 @@ export default class GithubMarkdownPrinter implements MarkdownPrinter { } headerLink(heading: string): string { - const slug = heading.replace(/[^a-z0-9]/gi, '-').toLowerCase(); + const slug = GithubSlugger.slug(heading); return `#${slug}`; } diff --git a/yarn.lock b/yarn.lock index 6c0ff99388..75746071c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3545,6 +3545,11 @@ resolved "https://registry.npmjs.org/@types/git-url-parse/-/git-url-parse-9.0.0.tgz#aac1315a44fa4ed5a52c3820f6c3c2fb79cbd12d" integrity sha512-kA2RxBT/r/ZuDDKwMl+vFWn1Z0lfm1/Ik6Qb91wnSzyzCDa/fkM8gIOq6ruB7xfr37n6Mj5dyivileUVKsidlg== +"@types/github-slugger@^1.3.0": + version "1.3.0" + resolved "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz#16ab393b30d8ae2a111ac748a015ac05a1fc5524" + integrity sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g== + "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" @@ -7889,6 +7894,11 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" +"emoji-regex@>=6.0.0 <=6.1.1": + version "6.1.1" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" + integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4= + emoji-regex@^7.0.1, emoji-regex@^7.0.2: version "7.0.3" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -9435,6 +9445,13 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" +github-slugger@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" + integrity sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q== + dependencies: + emoji-regex ">=6.0.0 <=6.1.1" + glob-base@^0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" From 85ca747cd367aa55c560fa11994f2c7755a0c555 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 17 Jul 2020 10:42:41 +0200 Subject: [PATCH 34/78] docgen: run prettier on generated docs --- packages/docgen/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 6e73dfeda5..f2675eef24 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -18,7 +18,7 @@ "build": "backstage-cli build --outputs cjs", "lint": "backstage-cli lint", "test": "backstage-cli test", - "docgen": "backstage-docgen generate --output ../../docs/reference/utility-apis --format github", + "docgen": "backstage-docgen generate --output ../../docs/reference/utility-apis --format github && prettier --write ../../docs/reference/utility-apis", "clean": "backstage-cli clean", "start": "nodemon --" }, From c75beb460df9b051bfebf1d12c5278b78780f8ea Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 17 Jul 2020 10:43:17 +0200 Subject: [PATCH 35/78] docs: regenerate api reference docs --- docs/reference/utility-apis/AlertApi.md | 17 ++-- docs/reference/utility-apis/AppThemeApi.md | 20 ++--- .../utility-apis/BackstageIdentityApi.md | 14 ++-- docs/reference/utility-apis/Config.md | 12 +-- docs/reference/utility-apis/ErrorApi.md | 21 +++-- .../reference/utility-apis/FeatureFlagsApi.md | 4 +- docs/reference/utility-apis/IdentityApi.md | 6 +- docs/reference/utility-apis/OAuthApi.md | 16 ++-- .../reference/utility-apis/OAuthRequestApi.md | 22 ++--- .../utility-apis/OpenIdConnectApi.md | 10 +-- docs/reference/utility-apis/ProfileInfoApi.md | 16 ++-- docs/reference/utility-apis/README.md | 80 ++++++++++--------- .../reference/utility-apis/SessionStateApi.md | 24 +++--- docs/reference/utility-apis/StorageApi.md | 18 ++--- 14 files changed, 141 insertions(+), 139 deletions(-) diff --git a/docs/reference/utility-apis/AlertApi.md b/docs/reference/utility-apis/AlertApi.md index b7d1a4d65d..1f0db84e29 100644 --- a/docs/reference/utility-apis/AlertApi.md +++ b/docs/reference/utility-apis/AlertApi.md @@ -1,10 +1,9 @@ # AlertApi The AlertApi type is defined at -[packages/core-api/src/apis/definitions/AlertApi.ts:29](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AlertApi.ts#L29). +[packages/core-api/src/apis/definitions/AlertApi.ts:29](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/AlertApi.ts#L29). -The following Utility API implements this type: -[alertApiRef](./README.md#alertapiref) +The following Utility API implements this type: [alertApiRef](./README.md#alert) ## Members @@ -39,9 +38,9 @@ export type AlertMessage = { Defined at -[packages/core-api/src/apis/definitions/AlertApi.ts:19](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AlertApi.ts#L19). +[packages/core-api/src/apis/definitions/AlertApi.ts:19](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/AlertApi.ts#L19). -Referenced by: [post](#post), [alert\$](#alert-). +Referenced by: [post](#post), [alert\$](#alert). ### Observable @@ -68,9 +67,9 @@ export type Observable<T> = { Defined at -[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L53). -Referenced by: [alert\$](#alert-). +Referenced by: [alert\$](#alert). ### Observer @@ -87,7 +86,7 @@ export type Observer<T> = { Defined at -[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L24). Referenced by: [Observable](#observable). @@ -110,6 +109,6 @@ export type Subscription = { Defined at -[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L33). Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/AppThemeApi.md b/docs/reference/utility-apis/AppThemeApi.md index 79d41ef6f6..02ab83afff 100644 --- a/docs/reference/utility-apis/AppThemeApi.md +++ b/docs/reference/utility-apis/AppThemeApi.md @@ -1,10 +1,10 @@ # AppThemeApi The AppThemeApi type is defined at -[packages/core-api/src/apis/definitions/AppThemeApi.ts:50](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AppThemeApi.ts#L50). +[packages/core-api/src/apis/definitions/AppThemeApi.ts:50](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/AppThemeApi.ts#L50). The following Utility API implements this type: -[appThemeApiRef](./README.md#appthemeapiref) +[appThemeApiRef](./README.md#apptheme) ## Members @@ -76,7 +76,7 @@ export type AppTheme = { Defined at -[packages/core-api/src/apis/definitions/AppThemeApi.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AppThemeApi.ts#L24). +[packages/core-api/src/apis/definitions/AppThemeApi.ts:24](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/AppThemeApi.ts#L24). Referenced by: [getInstalledThemes](#getinstalledthemes). @@ -87,7 +87,7 @@ export type BackstagePalette = Palette & Palette Defined at -[packages/theme/src/types.ts:63](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/theme/src/types.ts#L63). +[packages/theme/src/types.ts:63](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/theme/src/types.ts#L63). Referenced by: [BackstageTheme](#backstagetheme). @@ -100,7 +100,7 @@ export interface BackstageTheme extends Theme { Defined at -[packages/theme/src/types.ts:66](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/theme/src/types.ts#L66). +[packages/theme/src/types.ts:66](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/theme/src/types.ts#L66). Referenced by: [AppTheme](#apptheme). @@ -129,9 +129,9 @@ export type Observable<T> = { Defined at -[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L53). -Referenced by: [activeThemeId\$](#activethemeid-). +Referenced by: [activeThemeId\$](#activethemeid). ### Observer @@ -148,7 +148,7 @@ export type Observer<T> = { Defined at -[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L24). Referenced by: [Observable](#observable). @@ -197,7 +197,7 @@ type PaletteAdditions = { Defined at -[packages/theme/src/types.ts:23](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/theme/src/types.ts#L23). +[packages/theme/src/types.ts:23](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/theme/src/types.ts#L23). Referenced by: [BackstagePalette](#backstagepalette). @@ -220,6 +220,6 @@ export type Subscription = { Defined at -[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L33). Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/BackstageIdentityApi.md b/docs/reference/utility-apis/BackstageIdentityApi.md index 717a294621..a9e0203403 100644 --- a/docs/reference/utility-apis/BackstageIdentityApi.md +++ b/docs/reference/utility-apis/BackstageIdentityApi.md @@ -1,17 +1,17 @@ # BackstageIdentityApi The BackstageIdentityApi type is defined at -[packages/core-api/src/apis/definitions/auth.ts:144](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L144). +[packages/core-api/src/apis/definitions/auth.ts:144](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L144). The following Utility APIs implement this type: -- [githubAuthApiRef](./README.md#githubauthapiref) +- [githubAuthApiRef](./README.md#githubauth) -- [gitlabAuthApiRef](./README.md#gitlabauthapiref) +- [gitlabAuthApiRef](./README.md#gitlabauth) -- [googleAuthApiRef](./README.md#googleauthapiref) +- [googleAuthApiRef](./README.md#googleauth) -- [oktaAuthApiRef](./README.md#oktaauthapiref) +- [oktaAuthApiRef](./README.md#oktaauth) ## Members @@ -62,7 +62,7 @@ export type AuthRequestOptions = { Defined at -[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L40). +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L40). Referenced by: [getBackstageIdentity](#getbackstageidentity). @@ -83,6 +83,6 @@ export type BackstageIdentity = { Defined at -[packages/core-api/src/apis/definitions/auth.ts:157](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L157). +[packages/core-api/src/apis/definitions/auth.ts:157](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L157). Referenced by: [getBackstageIdentity](#getbackstageidentity). diff --git a/docs/reference/utility-apis/Config.md b/docs/reference/utility-apis/Config.md index ef53c5adcf..bca590bc9b 100644 --- a/docs/reference/utility-apis/Config.md +++ b/docs/reference/utility-apis/Config.md @@ -1,10 +1,10 @@ # Config The Config type is defined at -[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/config/src/types.ts#L32). +[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/config/src/types.ts#L32). The following Utility API implements this type: -[configApiRef](./README.md#configapiref) +[configApiRef](./README.md#config) ## Members @@ -132,7 +132,7 @@ export type Config = { Defined at -[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/config/src/types.ts#L32). +[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/config/src/types.ts#L32). Referenced by: [getConfig](#getconfig), [getOptionalConfig](#getoptionalconfig), [getConfigArray](#getconfigarray), @@ -145,7 +145,7 @@ export type JsonArray = JsonValue[] Defined at -[packages/config/src/types.ts:18](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/config/src/types.ts#L18). +[packages/config/src/types.ts:18](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/config/src/types.ts#L18). Referenced by: [JsonValue](#jsonvalue). @@ -156,7 +156,7 @@ export type JsonObject = { [key in string]?: JsonValue Defined at -[packages/config/src/types.ts:17](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/config/src/types.ts#L17). +[packages/config/src/types.ts:17](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/config/src/types.ts#L17). Referenced by: [JsonValue](#jsonvalue). @@ -173,7 +173,7 @@ export type JsonValue = Defined at -[packages/config/src/types.ts:19](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/config/src/types.ts#L19). +[packages/config/src/types.ts:19](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/config/src/types.ts#L19). Referenced by: [get](#get), [getOptional](#getoptional), [JsonObject](#jsonobject), [JsonArray](#jsonarray), [Config](#config). diff --git a/docs/reference/utility-apis/ErrorApi.md b/docs/reference/utility-apis/ErrorApi.md index efd4a3ed00..10ae47e6df 100644 --- a/docs/reference/utility-apis/ErrorApi.md +++ b/docs/reference/utility-apis/ErrorApi.md @@ -1,10 +1,9 @@ # ErrorApi The ErrorApi type is defined at -[packages/core-api/src/apis/definitions/ErrorApi.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/ErrorApi.ts#L53). +[packages/core-api/src/apis/definitions/ErrorApi.ts:53](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/ErrorApi.ts#L53). -The following Utility API implements this type: -[errorApiRef](./README.md#errorapiref) +The following Utility API implements this type: [errorApiRef](./README.md#error) ## Members @@ -42,9 +41,9 @@ type Error = { Defined at -[packages/core-api/src/apis/definitions/ErrorApi.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/ErrorApi.ts#L24). +[packages/core-api/src/apis/definitions/ErrorApi.ts:24](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/ErrorApi.ts#L24). -Referenced by: [post](#post), [error\$](#error-). +Referenced by: [post](#post), [error\$](#error). ### ErrorContext @@ -59,9 +58,9 @@ export type ErrorContext = { Defined at -[packages/core-api/src/apis/definitions/ErrorApi.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/ErrorApi.ts#L33). +[packages/core-api/src/apis/definitions/ErrorApi.ts:33](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/ErrorApi.ts#L33). -Referenced by: [post](#post), [error\$](#error-). +Referenced by: [post](#post), [error\$](#error). ### Observable @@ -88,9 +87,9 @@ export type Observable<T> = { Defined at -[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L53). -Referenced by: [error\$](#error-). +Referenced by: [error\$](#error). ### Observer @@ -107,7 +106,7 @@ export type Observer<T> = { Defined at -[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L24). Referenced by: [Observable](#observable). @@ -130,6 +129,6 @@ export type Subscription = { Defined at -[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L33). Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/FeatureFlagsApi.md b/docs/reference/utility-apis/FeatureFlagsApi.md index b3320b404a..5069c9272c 100644 --- a/docs/reference/utility-apis/FeatureFlagsApi.md +++ b/docs/reference/utility-apis/FeatureFlagsApi.md @@ -1,10 +1,10 @@ # FeatureFlagsApi The FeatureFlagsApi type is defined at -[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:41](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L41). +[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:41](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L41). The following Utility API implements this type: -[featureFlagsApiRef](./README.md#featureflagsapiref) +[featureFlagsApiRef](./README.md#featureflags) ## Members diff --git a/docs/reference/utility-apis/IdentityApi.md b/docs/reference/utility-apis/IdentityApi.md index f5de414cf5..2c94b7fcd0 100644 --- a/docs/reference/utility-apis/IdentityApi.md +++ b/docs/reference/utility-apis/IdentityApi.md @@ -1,10 +1,10 @@ # IdentityApi The IdentityApi type is defined at -[packages/core-api/src/apis/definitions/IdentityApi.ts:22](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/IdentityApi.ts#L22). +[packages/core-api/src/apis/definitions/IdentityApi.ts:22](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/IdentityApi.ts#L22). The following Utility API implements this type: -[identityApiRef](./README.md#identityapiref) +[identityApiRef](./README.md#identity) ## Members @@ -76,6 +76,6 @@ export type ProfileInfo = { Defined at -[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L172). +[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L172). Referenced by: [getProfile](#getprofile). diff --git a/docs/reference/utility-apis/OAuthApi.md b/docs/reference/utility-apis/OAuthApi.md index e3334601f1..09a4c4b1b4 100644 --- a/docs/reference/utility-apis/OAuthApi.md +++ b/docs/reference/utility-apis/OAuthApi.md @@ -1,19 +1,19 @@ # OAuthApi The OAuthApi type is defined at -[packages/core-api/src/apis/definitions/auth.ts:67](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L67). +[packages/core-api/src/apis/definitions/auth.ts:67](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L67). The following Utility APIs implement this type: -- [githubAuthApiRef](./README.md#githubauthapiref) +- [githubAuthApiRef](./README.md#githubauth) -- [gitlabAuthApiRef](./README.md#gitlabauthapiref) +- [gitlabAuthApiRef](./README.md#gitlabauth) -- [googleAuthApiRef](./README.md#googleauthapiref) +- [googleAuthApiRef](./README.md#googleauth) -- [oauth2ApiRef](./README.md#oauth2apiref) +- [oauth2ApiRef](./README.md#oauth2) -- [oktaAuthApiRef](./README.md#oktaauthapiref) +- [oktaAuthApiRef](./README.md#oktaauth) ## Members @@ -88,7 +88,7 @@ export type AuthRequestOptions = { Defined at -[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L40). +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L40). Referenced by: [getAccessToken](#getaccesstoken). @@ -114,6 +114,6 @@ export type OAuthScope = string | string[] Defined at -[packages/core-api/src/apis/definitions/auth.ts:38](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L38). +[packages/core-api/src/apis/definitions/auth.ts:38](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L38). Referenced by: [getAccessToken](#getaccesstoken). diff --git a/docs/reference/utility-apis/OAuthRequestApi.md b/docs/reference/utility-apis/OAuthRequestApi.md index 895c515fb0..fc38ca827b 100644 --- a/docs/reference/utility-apis/OAuthRequestApi.md +++ b/docs/reference/utility-apis/OAuthRequestApi.md @@ -1,10 +1,10 @@ # OAuthRequestApi The OAuthRequestApi type is defined at -[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:99](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L99). +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:99](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L99). The following Utility API implements this type: -[oauthRequestApiRef](./README.md#oauthrequestapiref) +[oauthRequestApiRef](./README.md#oauthrequest) ## Members @@ -72,7 +72,7 @@ export type AuthProvider = { Defined at -[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:27](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L27). +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:27](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L27). Referenced by: [AuthRequesterOptions](#authrequesteroptions), [PendingAuthRequest](#pendingauthrequest). @@ -96,7 +96,7 @@ export type AuthRequester<AuthResponse> = ( Defined at -[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:66](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L66). +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:66](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L66). Referenced by: [createAuthRequester](#createauthrequester). @@ -121,7 +121,7 @@ export type AuthRequesterOptions<AuthResponse> = { Defined at -[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:43](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L43). +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:43](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L43). Referenced by: [createAuthRequester](#createauthrequester). @@ -150,9 +150,9 @@ export type Observable<T> = { Defined at -[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L53). -Referenced by: [authRequest\$](#authrequest-). +Referenced by: [authRequest\$](#authrequest). ### Observer @@ -169,7 +169,7 @@ export type Observer<T> = { Defined at -[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L24). Referenced by: [Observable](#observable). @@ -204,9 +204,9 @@ export type PendingAuthRequest = { Defined at -[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:77](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L77). +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:77](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L77). -Referenced by: [authRequest\$](#authrequest-). +Referenced by: [authRequest\$](#authrequest). ### Subscription @@ -227,6 +227,6 @@ export type Subscription = { Defined at -[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L33). Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/OpenIdConnectApi.md b/docs/reference/utility-apis/OpenIdConnectApi.md index c626ccf3a1..ecbf439b84 100644 --- a/docs/reference/utility-apis/OpenIdConnectApi.md +++ b/docs/reference/utility-apis/OpenIdConnectApi.md @@ -1,15 +1,15 @@ # OpenIdConnectApi The OpenIdConnectApi type is defined at -[packages/core-api/src/apis/definitions/auth.ts:104](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L104). +[packages/core-api/src/apis/definitions/auth.ts:104](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L104). The following Utility APIs implement this type: -- [googleAuthApiRef](./README.md#googleauthapiref) +- [googleAuthApiRef](./README.md#googleauth) -- [oauth2ApiRef](./README.md#oauth2apiref) +- [oauth2ApiRef](./README.md#oauth2) -- [oktaAuthApiRef](./README.md#oktaauthapiref) +- [oktaAuthApiRef](./README.md#oktaauth) ## Members @@ -70,6 +70,6 @@ export type AuthRequestOptions = { Defined at -[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L40). +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L40). Referenced by: [getIdToken](#getidtoken). diff --git a/docs/reference/utility-apis/ProfileInfoApi.md b/docs/reference/utility-apis/ProfileInfoApi.md index 6fcbc99950..469c923cb0 100644 --- a/docs/reference/utility-apis/ProfileInfoApi.md +++ b/docs/reference/utility-apis/ProfileInfoApi.md @@ -1,19 +1,19 @@ # ProfileInfoApi The ProfileInfoApi type is defined at -[packages/core-api/src/apis/definitions/auth.ts:127](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L127). +[packages/core-api/src/apis/definitions/auth.ts:127](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L127). The following Utility APIs implement this type: -- [githubAuthApiRef](./README.md#githubauthapiref) +- [githubAuthApiRef](./README.md#githubauth) -- [gitlabAuthApiRef](./README.md#gitlabauthapiref) +- [gitlabAuthApiRef](./README.md#gitlabauth) -- [googleAuthApiRef](./README.md#googleauthapiref) +- [googleAuthApiRef](./README.md#googleauth) -- [oauth2ApiRef](./README.md#oauth2apiref) +- [oauth2ApiRef](./README.md#oauth2) -- [oktaAuthApiRef](./README.md#oktaauthapiref) +- [oktaAuthApiRef](./README.md#oktaauth) ## Members @@ -61,7 +61,7 @@ export type AuthRequestOptions = { Defined at -[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L40). +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L40). Referenced by: [getProfile](#getprofile). @@ -89,6 +89,6 @@ export type ProfileInfo = { Defined at -[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L172). +[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L172). Referenced by: [getProfile](#getprofile). diff --git a/docs/reference/utility-apis/README.md b/docs/reference/utility-apis/README.md index 398d55f605..66da8403b0 100644 --- a/docs/reference/utility-apis/README.md +++ b/docs/reference/utility-apis/README.md @@ -9,127 +9,131 @@ https://github.com/spotify/backstage/blob/master/docs/api/utility-apis.md. Used to report alerts and forward them to the app -Implemented type: [AlertApi](./AlertApi) +Implemented type: [AlertApi](./AlertApi.md) ApiRef: -[alertApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AlertApi.ts#L41) +[alertApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/AlertApi.ts#L41) ### appTheme API Used to configure the app theme, and enumerate options -Implemented type: [AppThemeApi](./AppThemeApi) +Implemented type: [AppThemeApi](./AppThemeApi.md) ApiRef: -[appThemeApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/AppThemeApi.ts#L74) +[appThemeApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/AppThemeApi.ts#L74) ### config Used to access runtime configuration -Implemented type: [Config](./Config) +Implemented type: [Config](./Config.md) ApiRef: -[configApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/ConfigApi.ts#L22) +[configApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/ConfigApi.ts#L22) ### error Used to report errors and forward them to the app -Implemented type: [ErrorApi](./ErrorApi) +Implemented type: [ErrorApi](./ErrorApi.md) ApiRef: -[errorApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/ErrorApi.ts#L65) +[errorApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/ErrorApi.ts#L65) ### featureFlags Used to toggle functionality in features across Backstage -Implemented type: [FeatureFlagsApi](./FeatureFlagsApi) +Implemented type: [FeatureFlagsApi](./FeatureFlagsApi.md) ApiRef: -[featureFlagsApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L58) +[featureFlagsApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L58) ### githubAuth Provides authentication towards Github APIs -Implemented types: [OAuthApi](./OAuthApi), [ProfileInfoApi](./ProfileInfoApi), -[BackstageIdentityApi](./BackstageIdentityApi), -[SessionStateApi](./SessionStateApi) +Implemented types: [OAuthApi](./OAuthApi.md), +[ProfileInfoApi](./ProfileInfoApi.md), +[BackstageIdentityApi](./BackstageIdentityApi.md), +[SessionStateApi](./SessionStateApi.md) ApiRef: -[githubAuthApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L230) +[githubAuthApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L230) ### gitlabAuth Provides authentication towards Gitlab APIs -Implemented types: [OAuthApi](./OAuthApi), [ProfileInfoApi](./ProfileInfoApi), -[BackstageIdentityApi](./BackstageIdentityApi), -[SessionStateApi](./SessionStateApi) +Implemented types: [OAuthApi](./OAuthApi.md), +[ProfileInfoApi](./ProfileInfoApi.md), +[BackstageIdentityApi](./BackstageIdentityApi.md), +[SessionStateApi](./SessionStateApi.md) ApiRef: -[gitlabAuthApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L260) +[gitlabAuthApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L260) ### googleAuth Provides authentication towards Google APIs and identities -Implemented types: [OAuthApi](./OAuthApi), -[OpenIdConnectApi](./OpenIdConnectApi), [ProfileInfoApi](./ProfileInfoApi), -[BackstageIdentityApi](./BackstageIdentityApi), -[SessionStateApi](./SessionStateApi) +Implemented types: [OAuthApi](./OAuthApi.md), +[OpenIdConnectApi](./OpenIdConnectApi.md), +[ProfileInfoApi](./ProfileInfoApi.md), +[BackstageIdentityApi](./BackstageIdentityApi.md), +[SessionStateApi](./SessionStateApi.md) ApiRef: -[googleAuthApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L213) +[googleAuthApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L213) ### identity Provides access to the identity of the signed in user -Implemented type: [IdentityApi](./IdentityApi) +Implemented type: [IdentityApi](./IdentityApi.md) ApiRef: -[identityApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/IdentityApi.ts#L54) +[identityApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/IdentityApi.ts#L54) ### oauth2 Example of how to use oauth2 custom provider -Implemented types: [OAuthApi](./OAuthApi), -[OpenIdConnectApi](./OpenIdConnectApi), [ProfileInfoApi](./ProfileInfoApi), -[SessionStateApi](./SessionStateApi) +Implemented types: [OAuthApi](./OAuthApi.md), +[OpenIdConnectApi](./OpenIdConnectApi.md), +[ProfileInfoApi](./ProfileInfoApi.md), [SessionStateApi](./SessionStateApi.md) ApiRef: -[oauth2ApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L270) +[oauth2ApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L270) ### oauthRequest An API for implementing unified OAuth flows in Backstage -Implemented type: [OAuthRequestApi](./OAuthRequestApi) +Implemented type: [OAuthRequestApi](./OAuthRequestApi.md) ApiRef: -[oauthRequestApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L130) +[oauthRequestApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L130) ### oktaAuth Provides authentication towards Okta APIs -Implemented types: [OAuthApi](./OAuthApi), -[OpenIdConnectApi](./OpenIdConnectApi), [ProfileInfoApi](./ProfileInfoApi), -[BackstageIdentityApi](./BackstageIdentityApi), -[SessionStateApi](./SessionStateApi) +Implemented types: [OAuthApi](./OAuthApi.md), +[OpenIdConnectApi](./OpenIdConnectApi.md), +[ProfileInfoApi](./ProfileInfoApi.md), +[BackstageIdentityApi](./BackstageIdentityApi.md), +[SessionStateApi](./SessionStateApi.md) ApiRef: -[oktaAuthApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L243) +[oktaAuthApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L243) ### storage Provides the ability to store data which is unique to the user -Implemented type: [StorageApi](./StorageApi) +Implemented type: [StorageApi](./StorageApi.md) ApiRef: -[storageApiRef](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/StorageApi.ts#L68) +[storageApiRef](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/StorageApi.ts#L68) diff --git a/docs/reference/utility-apis/SessionStateApi.md b/docs/reference/utility-apis/SessionStateApi.md index d15074aa9d..7ed67cbc62 100644 --- a/docs/reference/utility-apis/SessionStateApi.md +++ b/docs/reference/utility-apis/SessionStateApi.md @@ -1,19 +1,19 @@ # SessionStateApi The SessionStateApi type is defined at -[packages/core-api/src/apis/definitions/auth.ts:201](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L201). +[packages/core-api/src/apis/definitions/auth.ts:201](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L201). The following Utility APIs implement this type: -- [githubAuthApiRef](./README.md#githubauthapiref) +- [githubAuthApiRef](./README.md#githubauth) -- [gitlabAuthApiRef](./README.md#gitlabauthapiref) +- [gitlabAuthApiRef](./README.md#gitlabauth) -- [googleAuthApiRef](./README.md#googleauthapiref) +- [googleAuthApiRef](./README.md#googleauth) -- [oauth2ApiRef](./README.md#oauth2apiref) +- [oauth2ApiRef](./README.md#oauth2) -- [oktaAuthApiRef](./README.md#oktaauthapiref) +- [oktaAuthApiRef](./README.md#oktaauth) ## Members @@ -52,9 +52,9 @@ export type Observable<T> = { Defined at -[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L53). -Referenced by: [sessionState\$](#sessionstate-). +Referenced by: [sessionState\$](#sessionstate). ### Observer @@ -71,7 +71,7 @@ export type Observer<T> = { Defined at -[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L24). Referenced by: [Observable](#observable). @@ -87,9 +87,9 @@ export enum SessionState { Defined at -[packages/core-api/src/apis/definitions/auth.ts:192](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/auth.ts#L192). +[packages/core-api/src/apis/definitions/auth.ts:192](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/auth.ts#L192). -Referenced by: [sessionState\$](#sessionstate-). +Referenced by: [sessionState\$](#sessionstate). ### Subscription @@ -110,6 +110,6 @@ export type Subscription = { Defined at -[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L33). Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/StorageApi.md b/docs/reference/utility-apis/StorageApi.md index dd366bcf18..1eadec1302 100644 --- a/docs/reference/utility-apis/StorageApi.md +++ b/docs/reference/utility-apis/StorageApi.md @@ -1,10 +1,10 @@ # StorageApi The StorageApi type is defined at -[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/StorageApi.ts#L31). +[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/StorageApi.ts#L31). The following Utility API implements this type: -[storageApiRef](./README.md#storageapiref) +[storageApiRef](./README.md#storage) ## Members @@ -79,9 +79,9 @@ export type Observable<T> = { Defined at -[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L53). +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L53). -Referenced by: [observe\$](#observe-), [StorageApi](#storageapi). +Referenced by: [observe\$](#observe), [StorageApi](#storageapi). ### Observer @@ -98,7 +98,7 @@ export type Observer<T> = { Defined at -[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L24). +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L24). Referenced by: [Observable](#observable). @@ -144,7 +144,7 @@ export interface StorageApi { Defined at -[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/StorageApi.ts#L31). +[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/StorageApi.ts#L31). Referenced by: [forBucket](#forbucket). @@ -158,9 +158,9 @@ export type StorageValueChange<T = any> = { Defined at -[packages/core-api/src/apis/definitions/StorageApi.ts:21](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/apis/definitions/StorageApi.ts#L21). +[packages/core-api/src/apis/definitions/StorageApi.ts:21](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/apis/definitions/StorageApi.ts#L21). -Referenced by: [observe\$](#observe-), [StorageApi](#storageapi). +Referenced by: [observe\$](#observe), [StorageApi](#storageapi). ### Subscription @@ -181,6 +181,6 @@ export type Subscription = { Defined at -[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/4df02a253f6903e1ca20184369f5655e2d49d893/packages/core-api/src/types.ts#L33). +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/53a229ea7576b1432835e54e41e0b9526038afa4/packages/core-api/src/types.ts#L33). Referenced by: [Observable](#observable). From 9e2606a89a7a4e7b8608cf820f83f645f2329df7 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Wed, 22 Jul 2020 16:44:14 +0200 Subject: [PATCH 36/78] feature(techdocs): added an example component in techdocs-backend containing a documentation reference --- .../examples/documented-component/docs/index.md | 3 +++ .../documented-component/documented-component.yaml | 11 +++++++++++ .../examples/documented-component/mkdocs.yml | 7 +++++++ plugins/techdocs-backend/package.json | 3 ++- plugins/techdocs-backend/scripts/mock-data.sh | 12 ++++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 plugins/techdocs-backend/examples/documented-component/docs/index.md create mode 100644 plugins/techdocs-backend/examples/documented-component/documented-component.yaml create mode 100644 plugins/techdocs-backend/examples/documented-component/mkdocs.yml create mode 100755 plugins/techdocs-backend/scripts/mock-data.sh diff --git a/plugins/techdocs-backend/examples/documented-component/docs/index.md b/plugins/techdocs-backend/examples/documented-component/docs/index.md new file mode 100644 index 0000000000..bf0895710f --- /dev/null +++ b/plugins/techdocs-backend/examples/documented-component/docs/index.md @@ -0,0 +1,3 @@ +# example docs + +This is a basic example of documentation. diff --git a/plugins/techdocs-backend/examples/documented-component/documented-component.yaml b/plugins/techdocs-backend/examples/documented-component/documented-component.yaml new file mode 100644 index 0000000000..64eba5598b --- /dev/null +++ b/plugins/techdocs-backend/examples/documented-component/documented-component.yaml @@ -0,0 +1,11 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: documented-component + description: A Service with TechDocs documentation + annotations: + spotify.com/techdocs-ref: 'dir:./' +spec: + type: service + lifecycle: experimental + owner: documented@example.com diff --git a/plugins/techdocs-backend/examples/documented-component/mkdocs.yml b/plugins/techdocs-backend/examples/documented-component/mkdocs.yml new file mode 100644 index 0000000000..deb46069d7 --- /dev/null +++ b/plugins/techdocs-backend/examples/documented-component/mkdocs.yml @@ -0,0 +1,7 @@ +site_name: 'example-docs' + +nav: + - Home: index.md + +plugins: + - techdocs-core \ No newline at end of file diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 03a6a8dcb9..b66509abe5 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -17,7 +17,8 @@ "test": "backstage-cli test", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.15", diff --git a/plugins/techdocs-backend/scripts/mock-data.sh b/plugins/techdocs-backend/scripts/mock-data.sh new file mode 100755 index 0000000000..0630490694 --- /dev/null +++ b/plugins/techdocs-backend/scripts/mock-data.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +for URL in \ + 'documented-component/documented-component.yaml' \ +; do \ + curl \ + --location \ + --request POST 'localhost:7000/catalog/locations' \ + --header 'Content-Type: application/json' \ + --data-raw "{\"type\": \"file\", \"target\": \"$(pwd)/examples/${URL}\"}" + echo +done From 1ac69ba3e36a79f5fcc0bfd1535980f7b91b52d1 Mon Sep 17 00:00:00 2001 From: Forrest Waters Date: Tue, 21 Jul 2020 13:28:46 -0500 Subject: [PATCH 37/78] implement gitlabapi processor for private gitlab repos --- .../src/ingestion/LocationReaders.ts | 3 +- .../processors/GitlabApiReaderProcessor.ts | 109 ++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts index 8ec575d6ec..9c29e45ca3 100644 --- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts +++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts @@ -27,6 +27,7 @@ import { EntityPolicyProcessor } from './processors/EntityPolicyProcessor'; import { FileReaderProcessor } from './processors/FileReaderProcessor'; import { GithubReaderProcessor } from './processors/GithubReaderProcessor'; import { GithubApiReaderProcessor } from './processors/GithubApiReaderProcessor'; +import { GitlabApiReaderProcessor } from './processors/GitlabApiReaderProcessor'; import { GitlabReaderProcessor } from './processors/GitlabReaderProcessor'; import { LocationRefProcessor } from './processors/LocationEntityProcessor'; import * as result from './processors/results'; @@ -59,6 +60,7 @@ export class LocationReaders implements LocationReader { new FileReaderProcessor(), new GithubReaderProcessor(), new GithubApiReaderProcessor(), + new GitlabApiReaderProcessor(), new GitlabReaderProcessor(), new YamlProcessor(), new EntityPolicyProcessor(entityPolicy), @@ -102,7 +104,6 @@ export class LocationReaders implements LocationReader { }); } } - if (newItems.length === 0) { return output; } diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts new file mode 100644 index 0000000000..00abaab430 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts @@ -0,0 +1,109 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity, LocationSpec } from '@backstage/catalog-model'; +import fetch, { RequestInit, HeadersInit } from 'node-fetch'; +import lodash from 'lodash'; +import yaml from 'yaml'; +import * as result from './results'; +import { LocationProcessor, LocationProcessorEmit } from './types'; + +export class GitlabApiReaderProcessor implements LocationProcessor { + private privateToken: string = process.env.GITLAB_PRIVATE_TOKEN || ''; + + getRequestOptions(): RequestInit { + const headers: HeadersInit = { 'PRIVATE-TOKEN': '' }; + if (this.privateToken !== '') { + headers['PRIVATE-TOKEN'] = this.privateToken; + } + + const requestOptions: RequestInit = { + headers, + }; + + return requestOptions; + } + + async readLocation( + location: LocationSpec, + optional: boolean, + emit: LocationProcessorEmit, + ): Promise { + if (location.type !== 'gitlab/api') { + return false; + } + + try { + // const url = this.buildRawUrl(location.target); + const url = new URL(location.target); + const response = await fetch(url.toString(), this.getRequestOptions()); + if (response.ok) { + const data = await response.buffer(); + emit(result.data(location, data)); + } else { + const message = `${location.target} could not be read as ${url}, ${response.status} ${response.statusText}`; + if (response.status === 404) { + if (!optional) { + emit(result.notFoundError(location, message)); + } + } else { + emit(result.generalError(location, message)); + } + } + } catch (e) { + const message = `Unable to read ${location.type} ${location.target}, ${e}`; + emit(result.generalError(location, message)); + } + return true; + } + + // We need our own parseData because the gitlab api url has `/raw?ref=branch` + // on the end which doesn't match the regex in YamlProcessor (.ya?ml$) + async parseData( + data: Buffer, + location: LocationSpec, + emit: LocationProcessorEmit, + ): Promise { + if (!location.target.match(/\.ya?ml/)) { + return false; + } + + let documents: yaml.Document.Parsed[]; + try { + documents = yaml.parseAllDocuments(data.toString('utf8')).filter(d => d); + } catch (e) { + emit(result.generalError(location, `Failed to parse YAML, ${e}`)); + return true; + } + + for (const document of documents) { + if (document.errors?.length) { + const message = `YAML error, ${document.errors[0]}`; + emit(result.generalError(location, message)); + } else { + const json = document.toJSON(); + if (lodash.isPlainObject(json)) { + emit(result.entity(location, json as Entity)); + } else { + const message = `Expected object at root, got ${typeof json}`; + emit(result.generalError(location, message)); + } + } + } + + return true; + } +} From a0787d59a48d67c5cdf8a457ee6996e0f59d7553 Mon Sep 17 00:00:00 2001 From: Forrest Waters Date: Wed, 22 Jul 2020 16:13:42 -0500 Subject: [PATCH 38/78] lookup gitlab project ID with gitlab API and add tests --- .../src/ingestion/LocationReaders.ts | 1 + .../GitlabApiReaderProcessor.test.ts | 94 ++++++++++++++++++ .../processors/GitlabApiReaderProcessor.ts | 96 ++++++++++++------- 3 files changed, 155 insertions(+), 36 deletions(-) create mode 100644 plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts index 9c29e45ca3..545465cde0 100644 --- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts +++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts @@ -104,6 +104,7 @@ export class LocationReaders implements LocationReader { }); } } + if (newItems.length === 0) { return output; } diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts new file mode 100644 index 0000000000..a429a0e6fb --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GitlabApiReaderProcessor } from './GitlabApiReaderProcessor'; + +describe('GitlabApiReaderProcessor', () => { + it('should build raw api', () => { + const processor = new GitlabApiReaderProcessor(); + + const tests = [ + { + target: + 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', + url: new URL( + 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml?ref=branch', + ), + err: undefined, + }, + { + target: + 'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', + url: new URL( + 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml?ref=branch', + ), + err: undefined, + }, + { + target: + 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/to/file.yaml', // Repo not in subgroup + url: new URL( + 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml?ref=branch', + ), + err: undefined, + }, + { + target: + 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/', + url: null, + err: + 'Incorrect url: https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/, Error: Gitlab url does not end in .ya?ml', + }, + ]; + + for (const test of tests) { + if (test.err) { + expect(() => processor.buildRawUrl(test.target, 12345)).toThrowError( + test.err, + ); + } else { + expect(processor.buildRawUrl(test.target, 12345)).toEqual(test.url); + } + } + }); + + it('should return request options', () => { + const tests = [ + { + token: '0123456789', + expect: { + headers: { + 'PRIVATE-TOKEN': '0123456789', + }, + }, + }, + { + token: '', + expect: { + headers: { + 'PRIVATE-TOKEN': '', + }, + }, + }, + ]; + + for (const test of tests) { + process.env.GITLAB_PRIVATE_TOKEN = test.token; + const processor = new GitlabApiReaderProcessor(); + expect(processor.getRequestOptions()).toEqual(test.expect); + } + }); +}); diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts index 00abaab430..6964640eb4 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts @@ -14,10 +14,8 @@ * limitations under the License. */ -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { LocationSpec } from '@backstage/catalog-model'; import fetch, { RequestInit, HeadersInit } from 'node-fetch'; -import lodash from 'lodash'; -import yaml from 'yaml'; import * as result from './results'; import { LocationProcessor, LocationProcessorEmit } from './types'; @@ -47,8 +45,8 @@ export class GitlabApiReaderProcessor implements LocationProcessor { } try { - // const url = this.buildRawUrl(location.target); - const url = new URL(location.target); + const projectID = await this.getProjectID(location.target); + const url = this.buildRawUrl(location.target, projectID); const response = await fetch(url.toString(), this.getRequestOptions()); if (response.ok) { const data = await response.buffer(); @@ -70,40 +68,66 @@ export class GitlabApiReaderProcessor implements LocationProcessor { return true; } - // We need our own parseData because the gitlab api url has `/raw?ref=branch` - // on the end which doesn't match the regex in YamlProcessor (.ya?ml$) - async parseData( - data: Buffer, - location: LocationSpec, - emit: LocationProcessorEmit, - ): Promise { - if (!location.target.match(/\.ya?ml/)) { - return false; - } - - let documents: yaml.Document.Parsed[]; + // convert https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath + // to https://gitlab.com/api/v4/projects//repository/files/filepath?ref=branch + buildRawUrl(target: string, projectID: Number): URL { try { - documents = yaml.parseAllDocuments(data.toString('utf8')).filter(d => d); - } catch (e) { - emit(result.generalError(location, `Failed to parse YAML, ${e}`)); - return true; - } + const url = new URL(target); - for (const document of documents) { - if (document.errors?.length) { - const message = `YAML error, ${document.errors[0]}`; - emit(result.generalError(location, message)); - } else { - const json = document.toJSON(); - if (lodash.isPlainObject(json)) { - emit(result.entity(location, json as Entity)); - } else { - const message = `Expected object at root, got ${typeof json}`; - emit(result.generalError(location, message)); - } + const branchAndfilePath = url.pathname.split('/-/blob/')[1]; + + if (!branchAndfilePath.match(/\.ya?ml$/)) { + throw new Error('Gitlab url does not end in .ya?ml'); } - } - return true; + const [branch, ...filePath] = branchAndfilePath.split('/'); + + url.pathname = [ + '/api/v4/projects', + projectID, + 'repository/files', + encodeURIComponent(filePath.join('/')), + 'raw', + ].join('/'); + url.search = `?ref=${branch}`; + + return url; + } catch (e) { + throw new Error(`Incorrect url: ${target}, ${e}`); + } + } + + async getProjectID(target: string): Promise { + const url = new URL(target); + + if ( + // absPaths to gitlab files should contain /-/blob + // ex: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath + !url.pathname.match(/\/\-\/blob\//) + ) { + throw new Error('Please provide full path to yaml file from Gitlab'); + } + try { + const repo = url.pathname.split('/-/blob/')[0]; + + // Find ProjectID from url + // convert 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath' + // to 'https://gitlab.com/api/v4/projects/groupA%2Fteams%2FsubgroupA%2FteamA%2Frepo' + const repoIDLookup = new URL( + `${url.protocol + url.hostname}/api/v4/projects/${encodeURIComponent( + repo.replace(/^\//, ''), + )}`, + ); + const response = await fetch( + repoIDLookup.toString(), + this.getRequestOptions(), + ); + const projectIDJson = await response.json(); + const projectID: Number = projectIDJson.id; + + return projectID; + } catch (e) { + throw new Error(`Could not get Gitlab ProjectID for: ${target}, ${e}`); + } } } From 360200387e5609119bfc6d47a84a86f8d864c8d6 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 23 Jul 2020 11:43:59 +0200 Subject: [PATCH 39/78] feat(gql): added a simple plugin for graphql stuff --- plugins/graphql/.eslintrc.js | 3 + plugins/graphql/README.md | 14 +++++ plugins/graphql/package.json | 40 ++++++++++++ plugins/graphql/scripts/mock-data.sh | 2 + plugins/graphql/src/index.ts | 18 ++++++ plugins/graphql/src/run.ts | 33 ++++++++++ plugins/graphql/src/service/router.test.ts | 45 ++++++++++++++ plugins/graphql/src/service/router.ts | 40 ++++++++++++ .../graphql/src/service/standaloneServer.ts | 62 +++++++++++++++++++ plugins/graphql/src/setupTests.ts | 16 +++++ yarn.lock | 7 ++- 11 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 plugins/graphql/.eslintrc.js create mode 100644 plugins/graphql/README.md create mode 100644 plugins/graphql/package.json create mode 100755 plugins/graphql/scripts/mock-data.sh create mode 100644 plugins/graphql/src/index.ts create mode 100644 plugins/graphql/src/run.ts create mode 100644 plugins/graphql/src/service/router.test.ts create mode 100644 plugins/graphql/src/service/router.ts create mode 100644 plugins/graphql/src/service/standaloneServer.ts create mode 100644 plugins/graphql/src/setupTests.ts diff --git a/plugins/graphql/.eslintrc.js b/plugins/graphql/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/graphql/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md new file mode 100644 index 0000000000..4a251363ce --- /dev/null +++ b/plugins/graphql/README.md @@ -0,0 +1,14 @@ +# graphql + +Welcome to the graphql backend plugin! + +_This plugin was created through the Backstage CLI_ + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn +start` in the root directory, and then navigating to [/graphql](http://localhost:3000/graphql). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json new file mode 100644 index 0000000000..c80007276d --- /dev/null +++ b/plugins/graphql/package.json @@ -0,0 +1,40 @@ +{ + "name": "@backstage/plugin-graphql-backend", + "version": "0.1.1-alpha.15", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" + }, + "dependencies": { + "@backstage/backend-common": "^0.1.1-alpha.15", + "@types/express": "^4.17.6", + "express": "^4.17.1", + "express-promise-router": "^3.0.3", + "yn": "^4.0.0", + "whatwg-fetch": "^3.2.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.15", + "@types/supertest": "^2.0.8", + "supertest": "^4.0.2", + "msw": "^0.19.5" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/graphql/scripts/mock-data.sh b/plugins/graphql/scripts/mock-data.sh new file mode 100755 index 0000000000..ff30921715 --- /dev/null +++ b/plugins/graphql/scripts/mock-data.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +echo "use this script to load your service with some mock data if needed!" diff --git a/plugins/graphql/src/index.ts b/plugins/graphql/src/index.ts new file mode 100644 index 0000000000..7332f92d68 --- /dev/null +++ b/plugins/graphql/src/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +require('whatwg-fetch'); +export * from './service/router'; diff --git a/plugins/graphql/src/run.ts b/plugins/graphql/src/run.ts new file mode 100644 index 0000000000..b96989e4b8 --- /dev/null +++ b/plugins/graphql/src/run.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getRootLogger } from '@backstage/backend-common'; +import yn from 'yn'; +import { startStandaloneServer } from './service/standaloneServer'; + +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); +}); diff --git a/plugins/graphql/src/service/router.test.ts b/plugins/graphql/src/service/router.test.ts new file mode 100644 index 0000000000..fba6b0ccdf --- /dev/null +++ b/plugins/graphql/src/service/router.test.ts @@ -0,0 +1,45 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import express from 'express'; +import request from 'supertest'; + +import { createRouter } from './router'; + +describe('createRouter', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET /ping', () => { + it('returns ok', async () => { + const response = await request(app).get('/ping'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + }); +}); diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts new file mode 100644 index 0000000000..3ea8219365 --- /dev/null +++ b/plugins/graphql/src/service/router.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { errorHandler } from '@backstage/backend-common'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; + +export interface RouterOptions { + logger: Logger; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + const { logger } = options; + + const router = Router(); + router.use(express.json()); + + router.get('/health', (_, response) => { + logger.info('PONG!'); + response.send({ status: 'ok' }); + }); + router.use(errorHandler()); + return router; +} diff --git a/plugins/graphql/src/service/standaloneServer.ts b/plugins/graphql/src/service/standaloneServer.ts new file mode 100644 index 0000000000..0f772a583f --- /dev/null +++ b/plugins/graphql/src/service/standaloneServer.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createServiceBuilder } from '@backstage/backend-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'graphql-backend' }); + logger.debug('Starting application server...'); + const router = await createRouter({ + logger, + }); + + const service = createServiceBuilder(module) + .enableCors({ origin: 'http://localhost:3000' }) + .addRouter('/graphql', router); + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/graphql/src/setupTests.ts b/plugins/graphql/src/setupTests.ts new file mode 100644 index 0000000000..aa5bc44d68 --- /dev/null +++ b/plugins/graphql/src/setupTests.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +require('whatwg-fetch'); diff --git a/yarn.lock b/yarn.lock index 6c0ff99388..cd0e39f7e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13127,7 +13127,7 @@ ms@^2.0.0, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -msw@^0.19.0: +msw@^0.19.0, msw@^0.19.5: version "0.19.5" resolved "https://registry.npmjs.org/msw/-/msw-0.19.5.tgz#7d2a1a852ccf1644d3db6735d69fff6777aac33f" integrity sha512-J5eQ++gDVZoHPC8gVXtWcakLjgmPipvFj/sEnlRV/WViXuiq2CamSqO3Wbh6H8bAmj+k2vUWCfcVT1HjMdKB2Q== @@ -19284,6 +19284,11 @@ whatwg-fetch@^2.0.4: resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== +whatwg-fetch@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.2.0.tgz#8e134f701f0a4ab5fda82626f113e2b647fd16dc" + integrity sha512-SdGPoQMMnzVYThUbSrEvqTlkvC1Ux27NehaJ/GUHBfNrh5Mjg+1/uRyFMwVnxO2MrikMWvWAqUGgQOfVU4hT7w== + whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" From 851b040c41f65d58223fafa02fbfea749cf00aa4 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Thu, 23 Jul 2020 14:14:30 +0200 Subject: [PATCH 40/78] changed spotify.com namespace to backstage.io --- .../examples/documented-component/documented-component.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/techdocs-backend/examples/documented-component/documented-component.yaml b/plugins/techdocs-backend/examples/documented-component/documented-component.yaml index 64eba5598b..d5a1214b15 100644 --- a/plugins/techdocs-backend/examples/documented-component/documented-component.yaml +++ b/plugins/techdocs-backend/examples/documented-component/documented-component.yaml @@ -4,7 +4,7 @@ metadata: name: documented-component description: A Service with TechDocs documentation annotations: - spotify.com/techdocs-ref: 'dir:./' + backstage.io/techdocs-ref: 'dir:./' spec: type: service lifecycle: experimental From 7808c115d1162912ceda0bcff6b8f09ffbd78ad4 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Thu, 23 Jul 2020 14:28:50 +0200 Subject: [PATCH 41/78] Added empty line at the end of file --- .../techdocs-backend/examples/documented-component/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/techdocs-backend/examples/documented-component/mkdocs.yml b/plugins/techdocs-backend/examples/documented-component/mkdocs.yml index deb46069d7..6a3f85d020 100644 --- a/plugins/techdocs-backend/examples/documented-component/mkdocs.yml +++ b/plugins/techdocs-backend/examples/documented-component/mkdocs.yml @@ -4,4 +4,4 @@ nav: - Home: index.md plugins: - - techdocs-core \ No newline at end of file + - techdocs-core From 61257d533ac1d1530a1f46cc0ab7fc11d75c45cc Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 23 Jul 2020 20:21:16 +0200 Subject: [PATCH 42/78] feat(graphql): added simple graphql service with new backend plugin --- packages/backend/package.json | 1 + packages/backend/src/index.ts | 5 ++- packages/backend/src/plugins/graphql.ts | 39 ++++++++++++++++++++++ plugins/catalog/package.json | 2 +- plugins/graphql/.eslintrc.js | 2 +- plugins/graphql/package.json | 9 ++--- plugins/graphql/src/index.ts | 1 + plugins/graphql/src/service/router.test.ts | 4 +-- plugins/graphql/src/setupTests.ts | 2 ++ yarn.lock | 7 +--- 10 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 packages/backend/src/plugins/graphql.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index 86d348d2a1..3ba1ad8ca5 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -30,6 +30,7 @@ "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.15", "@backstage/plugin-sentry-backend": "^0.1.1-alpha.15", "@backstage/plugin-techdocs-backend": "^0.1.1-alpha.15", + "@backstage/plugin-graphql-backend": "^0.1.1-alpha.15", "@octokit/rest": "^18.0.0", "dockerode": "^3.2.0", "express": "^4.17.1", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index d060791f98..7c98140270 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -38,6 +38,7 @@ import scaffolder from './plugins/scaffolder'; import sentry from './plugins/sentry'; import proxy from './plugins/proxy'; import techdocs from './plugins/techdocs'; +import graphql from './plugins/graphql'; import { PluginEnvironment } from './types'; function makeCreateEnv(loadedConfigs: AppConfig[]) { @@ -70,6 +71,7 @@ async function main() { const rollbarEnv = useHotMemoize(module, () => createEnv('rollbar')); const sentryEnv = useHotMemoize(module, () => createEnv('sentry')); const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); + const graphqlEnv = useHotMemoize(module, () => createEnv('graphql')); const service = createServiceBuilder(module) .loadConfig(configReader) @@ -80,7 +82,8 @@ async function main() { .addRouter('/auth', await auth(authEnv)) .addRouter('/identity', await identity(identityEnv)) .addRouter('/techdocs', await techdocs(techdocsEnv)) - .addRouter('/proxy', await proxy(proxyEnv)); + .addRouter('/proxy', await proxy(proxyEnv)) + .addRouter('/graphql', await graphql(graphqlEnv)); await service.start().catch(err => { console.log(err); diff --git a/packages/backend/src/plugins/graphql.ts b/packages/backend/src/plugins/graphql.ts new file mode 100644 index 0000000000..0f9b167d19 --- /dev/null +++ b/packages/backend/src/plugins/graphql.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createRouter } from '@backstage/plugin-graphql-backend'; +import type { PluginEnvironment } from '../types'; + +export default async function createPlugin({ logger }: PluginEnvironment) { + return await createRouter({ + logger, + }); +} diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 81a94ae58f..991dae7b46 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -50,7 +50,7 @@ "jest-fetch-mock": "^3.0.3", "msw": "^0.19.0", "react-test-renderer": "^16.13.1", - "whatwg-fetch": "^3.0.0" + "whatwg-fetch": "^2.0.0" }, "files": [ "dist" diff --git a/plugins/graphql/.eslintrc.js b/plugins/graphql/.eslintrc.js index 13573efa9c..16a033dbc6 100644 --- a/plugins/graphql/.eslintrc.js +++ b/plugins/graphql/.eslintrc.js @@ -1,3 +1,3 @@ module.exports = { - extends: [require.resolve('@backstage/cli/config/eslint')], + extends: [require.resolve('@backstage/cli/config/eslint.backend')], }; diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index c80007276d..f1850ff975 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -25,14 +25,15 @@ "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", - "yn": "^4.0.0", - "whatwg-fetch": "^3.2.0" + "whatwg-fetch": "^2.0.0", + "winston": "^3.2.1", + "yn": "^4.0.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.15", "@types/supertest": "^2.0.8", - "supertest": "^4.0.2", - "msw": "^0.19.5" + "msw": "^0.19.5", + "supertest": "^4.0.2" }, "files": [ "dist" diff --git a/plugins/graphql/src/index.ts b/plugins/graphql/src/index.ts index 7332f92d68..f12e894c84 100644 --- a/plugins/graphql/src/index.ts +++ b/plugins/graphql/src/index.ts @@ -15,4 +15,5 @@ */ require('whatwg-fetch'); + export * from './service/router'; diff --git a/plugins/graphql/src/service/router.test.ts b/plugins/graphql/src/service/router.test.ts index fba6b0ccdf..0aaeafa379 100644 --- a/plugins/graphql/src/service/router.test.ts +++ b/plugins/graphql/src/service/router.test.ts @@ -34,9 +34,9 @@ describe('createRouter', () => { jest.resetAllMocks(); }); - describe('GET /ping', () => { + describe('GET /health', () => { it('returns ok', async () => { - const response = await request(app).get('/ping'); + const response = await request(app).get('/health'); expect(response.status).toEqual(200); expect(response.body).toEqual({ status: 'ok' }); diff --git a/plugins/graphql/src/setupTests.ts b/plugins/graphql/src/setupTests.ts index aa5bc44d68..ac41ef995a 100644 --- a/plugins/graphql/src/setupTests.ts +++ b/plugins/graphql/src/setupTests.ts @@ -14,3 +14,5 @@ * limitations under the License. */ require('whatwg-fetch'); + +export {}; diff --git a/yarn.lock b/yarn.lock index cd0e39f7e1..346f99c321 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19279,16 +19279,11 @@ whatwg-fetch@3.0.0, whatwg-fetch@^3.0.0: resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== -whatwg-fetch@^2.0.4: +whatwg-fetch@^2.0.0, whatwg-fetch@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== -whatwg-fetch@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.2.0.tgz#8e134f701f0a4ab5fda82626f113e2b647fd16dc" - integrity sha512-SdGPoQMMnzVYThUbSrEvqTlkvC1Ux27NehaJ/GUHBfNrh5Mjg+1/uRyFMwVnxO2MrikMWvWAqUGgQOfVU4hT7w== - whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" From be2ed9b77ef04b311872548c708bcf49d1ebfcb0 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 23 Jul 2020 20:55:12 +0200 Subject: [PATCH 43/78] feat(graphq): started a simple graphql server --- plugins/graphql/package.json | 4 + plugins/graphql/src/schema.gql | 11 + plugins/graphql/src/service/router.test.ts | 45 - plugins/graphql/src/service/router.ts | 16 +- yarn.lock | 938 +++++++++++++++++++-- 5 files changed, 887 insertions(+), 127 deletions(-) create mode 100644 plugins/graphql/src/schema.gql delete mode 100644 plugins/graphql/src/service/router.test.ts diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index f1850ff975..be0681c18f 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -23,8 +23,11 @@ "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.15", "@types/express": "^4.17.6", + "apollo-server": "^2.16.0", + "apollo-server-express": "^2.16.0", "express": "^4.17.1", "express-promise-router": "^3.0.3", + "graphql": "^15.3.0", "whatwg-fetch": "^2.0.0", "winston": "^3.2.1", "yn": "^4.0.0" @@ -32,6 +35,7 @@ "devDependencies": { "@backstage/cli": "^0.1.1-alpha.15", "@types/supertest": "^2.0.8", + "eslint-plugin-graphql": "^4.0.0", "msw": "^0.19.5", "supertest": "^4.0.2" }, diff --git a/plugins/graphql/src/schema.gql b/plugins/graphql/src/schema.gql new file mode 100644 index 0000000000..a03bdbc317 --- /dev/null +++ b/plugins/graphql/src/schema.gql @@ -0,0 +1,11 @@ +type CatalogEntity { + id: String +} + +type CatalogQuery { + list: [CatalogEntity!]! +} + +type Query { + catalog: CatalogQuery +} diff --git a/plugins/graphql/src/service/router.test.ts b/plugins/graphql/src/service/router.test.ts deleted file mode 100644 index 0aaeafa379..0000000000 --- a/plugins/graphql/src/service/router.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getVoidLogger } from '@backstage/backend-common'; -import express from 'express'; -import request from 'supertest'; - -import { createRouter } from './router'; - -describe('createRouter', () => { - let app: express.Express; - - beforeAll(async () => { - const router = await createRouter({ - logger: getVoidLogger(), - }); - app = express().use(router); - }); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('GET /health', () => { - it('returns ok', async () => { - const response = await request(app).get('/health'); - - expect(response.status).toEqual(200); - expect(response.body).toEqual({ status: 'ok' }); - }); - }); -}); diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts index 3ea8219365..09ab5270b7 100644 --- a/plugins/graphql/src/service/router.ts +++ b/plugins/graphql/src/service/router.ts @@ -18,6 +18,9 @@ import { errorHandler } from '@backstage/backend-common'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; +import fs from 'fs'; +import path from 'path'; +import { ApolloServer } from 'apollo-server-express'; export interface RouterOptions { logger: Logger; @@ -26,15 +29,22 @@ export interface RouterOptions { export async function createRouter( options: RouterOptions, ): Promise { - const { logger } = options; + const typeDefs = await fs.promises.readFile( + path.resolve(__dirname, '..', 'schema.gql'), + 'utf-8', + ); + const server = new ApolloServer({ typeDefs, logger: options.logger }); const router = Router(); - router.use(express.json()); + + const apolloMiddlware = server.getMiddleware({ path: '/' }); + router.use(apolloMiddlware); router.get('/health', (_, response) => { - logger.info('PONG!'); response.send({ status: 'ok' }); }); + router.use(errorHandler()); + return router; } diff --git a/yarn.lock b/yarn.lock index 346f99c321..7f520c404f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,44 @@ # yarn lockfile v1 +"@apollo/protobufjs@^1.0.3": + version "1.0.4" + resolved "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.4.tgz#cf01747a55359066341f31b5ce8db17df44244e0" + integrity sha512-EE3zx+/D/wur/JiLp6VCiw1iYdyy1lCJMf8CGPkLeDt5QJrN4N8tKFx33Ah4V30AUQzMk7Uz4IXKZ1LOj124gA== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.0" + "@types/node" "^10.1.0" + long "^4.0.0" + +"@apollographql/apollo-tools@^0.4.3": + version "0.4.8" + resolved "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.4.8.tgz#d81da89ee880c2345eb86bddb92b35291f6135ed" + integrity sha512-W2+HB8Y7ifowcf3YyPHgDI05izyRtOeZ4MqIr7LbTArtmJ0ZHULWpn84SGMW7NAvTV1tFExpHlveHhnXuJfuGA== + dependencies: + apollo-env "^0.6.5" + +"@apollographql/graphql-playground-html@1.6.26": + version "1.6.26" + resolved "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.26.tgz#2f7b610392e2a872722912fc342b43cf8d641cb3" + integrity sha512-XAwXOIab51QyhBxnxySdK3nuMEUohhDsHQ5Rbco/V1vjlP75zZ0ZLHD9dTpXTN8uxKxopb2lUvJTq+M4g2Q0HQ== + dependencies: + xss "^1.0.6" + +"@ardatan/aggregate-error@0.0.1": + version "0.0.1" + resolved "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.1.tgz#1403ac5de10d8ca689fc1f65844c27179ae1d44f" + integrity sha512-UQ9BequOTIavs0pTHLMwQwKQF8tTV1oezY/H2O9chA+JNPFZSua55xpU5dPSjAU9/jLJ1VwU+HJuTVN8u7S6Fg== + "@babel/code-frame@7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" @@ -906,6 +944,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.0": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" + integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.3.3", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -1184,6 +1229,109 @@ unique-filename "^1.1.1" which "^1.3.1" +"@graphql-tools/delegate@6.0.15": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.15.tgz#9e060bfc31fe7735bd5b2b401e98dea3fa5d3b25" + integrity sha512-GG/zp29PMfG6eXpfe1M5C3U1EI1f3tJu2glFN8t0RIfp4FEgZs/PRvZuuep5orFge8dvX/LQpJY8Vl2JmU4WMg== + dependencies: + "@ardatan/aggregate-error" "0.0.1" + "@graphql-tools/schema" "6.0.15" + "@graphql-tools/utils" "6.0.15" + tslib "~2.0.0" + +"@graphql-tools/graphql-file-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.0.15.tgz#e186b8147397bd7510e1b3318f5d3f7d365fc4e1" + integrity sha512-QbCf731A2A2hrHP+cMSAKvY3D7IauFNqp5bAGdbLwSHRqaxUIfKi7Q76/9pZ3rN/e6yu/zVz+t1rkf7lT2/8OA== + dependencies: + "@graphql-tools/import" "6.0.15" + "@graphql-tools/utils" "6.0.15" + fs-extra "9.0.1" + tslib "~2.0.0" + +"@graphql-tools/import@6.0.15": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/import/-/import-6.0.15.tgz#d6a9d3b6199a1e07d80d8021635d317b2c190c6f" + integrity sha512-YaQizD031nlrObiAJj+DO+0Wf2ompR2G5OFNQZIOgUlm1+kfH3GPIFoE5Ww74YH6vy9s4UyYYeZJz6APxPdMzg== + dependencies: + fs-extra "9.0.1" + resolve-from "5.0.0" + +"@graphql-tools/json-file-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-6.0.15.tgz#e341a5fed1ef3af8f8bfde17ed9a14e1a6df36c0" + integrity sha512-SQO7w+KPxW6Q3snE3G4eNOA8CcBBDYHpk8JILj93oe4BassuPY5NCUOeZ+2PYczwZQbTNDQXeW1oQou44U1aBg== + dependencies: + "@graphql-tools/utils" "6.0.15" + fs-extra "9.0.1" + tslib "~2.0.0" + +"@graphql-tools/load@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/load/-/load-6.0.15.tgz#f2012b8938b3a535dbafefbb719997684028f30f" + integrity sha512-STH3ZjbViRqDyCw+f7PZrnDs6yhP7m2l4x5lJBMyMeLaLwuO1z+WhgtqYZNpCYlQY2jNSLXWCa0nWmpYvdLnlA== + dependencies: + "@graphql-tools/merge" "6.0.15" + "@graphql-tools/utils" "6.0.15" + globby "11.0.1" + import-from "3.0.0" + is-glob "4.0.1" + p-limit "3.0.2" + tslib "~2.0.0" + unixify "1.0.0" + valid-url "1.0.9" + +"@graphql-tools/merge@6.0.15", "@graphql-tools/merge@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.0.15.tgz#09c84bd08971edfb3e1035d71432b47687820bdc" + integrity sha512-qusTLzkf6GtxS6LRQnEAWIwA1BeJj5SkZ2pnE4/wVe9gs0grpEsOKYxvGpBi8IZR7r8UeNpkdgk2HP0jlq/WWA== + dependencies: + "@graphql-tools/schema" "6.0.15" + "@graphql-tools/utils" "6.0.15" + tslib "~2.0.0" + +"@graphql-tools/schema@6.0.15": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/schema/-/schema-6.0.15.tgz#b016f9f36820342982887a291baa7e7d11b039ae" + integrity sha512-Wo+d6/OPjeXjwB1pcqsWmqLdweGH+BVhvKe/YPQA/uiWr8ikgShvNLNiuF03gc/1AMR487A09XcPEyabRKJLew== + dependencies: + "@graphql-tools/utils" "6.0.15" + tslib "~2.0.0" + +"@graphql-tools/url-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-6.0.15.tgz#3ad621ed75c03fbc0ea08d2560f379a053c4035f" + integrity sha512-/iGuK7J9yCECYMYQJqKNWnz4ytPHppkxh4YS5Ud9QPDNl488e+eInyNbkdiWcFGyZ4KHqEnXSDdRFg3mFNrMnw== + dependencies: + "@graphql-tools/delegate" "6.0.15" + "@graphql-tools/utils" "6.0.15" + "@graphql-tools/wrap" "6.0.15" + "@types/websocket" "1.0.1" + cross-fetch "3.0.5" + subscriptions-transport-ws "0.9.17" + tslib "~2.0.0" + valid-url "1.0.9" + websocket "1.0.31" + +"@graphql-tools/utils@6.0.15", "@graphql-tools/utils@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.15.tgz#6d54d383285bea3c22797531933b62a408e78e49" + integrity sha512-VG5cMLPgh9RDLGHamGpXVnBrNw7bZGT46LrxK7IIqDZI9H0GPsRCo8+p+CfDkw0IlDiEECb624WVCpm9IYNecA== + dependencies: + "@ardatan/aggregate-error" "0.0.1" + camel-case "4.1.1" + +"@graphql-tools/wrap@6.0.15": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.0.15.tgz#1b4a43d406f3894e4cbc931f9d4e0d282ab0076e" + integrity sha512-yWiDBrbzml6PRl4aeJBLNGPw385LFtszMfkfYwjLSWvNyVILDCMa/XWHThw4FMaZ1nPL0GuLggW2bVkUBi3TYA== + dependencies: + "@graphql-tools/delegate" "6.0.15" + "@graphql-tools/schema" "6.0.15" + "@graphql-tools/utils" "6.0.15" + aggregate-error "3.0.1" + tslib "~2.0.0" + "@hapi/address@^2.1.2": version "2.1.4" resolved "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" @@ -2444,6 +2592,59 @@ resolved "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + "@reach/router@^1.2.1": version "1.3.3" resolved "https://registry.npmjs.org/@reach/router/-/router-1.3.3.tgz#58162860dce6c9449d49be86b0561b5ef46d80db" @@ -3333,6 +3534,13 @@ "@theme-ui/core" "^0.3.1" "@theme-ui/mdx" "^0.3.0" +"@types/accepts@*", "@types/accepts@^1.3.5": + version "1.3.5" + resolved "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" + integrity sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ== + dependencies: + "@types/node" "*" + "@types/anymatch@*": version "1.3.1" resolved "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -3371,7 +3579,7 @@ dependencies: "@babel/types" "^7.3.0" -"@types/body-parser@*", "@types/body-parser@^1.19.0": +"@types/body-parser@*", "@types/body-parser@1.19.0", "@types/body-parser@^1.19.0": version "1.19.0" resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== @@ -3439,6 +3647,11 @@ dependencies: "@types/node" "*" +"@types/content-disposition@*": + version "0.5.3" + resolved "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.3.tgz#0aa116701955c2faa0717fc69cd1596095e49d96" + integrity sha512-P1bffQfhD3O4LW0ioENXUhZ9OIa0Zn+P7M+pWgkCKaT53wVLSq0mrKksCID/FGHpFhRSxRGhgrQmfhRuzwtKdg== + "@types/cookie-parser@^1.4.2": version "1.4.2" resolved "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz#e4d5c5ffda82b80672a88a4281aaceefb1bd9df5" @@ -3456,7 +3669,17 @@ resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw== -"@types/cors@^2.8.6": +"@types/cookies@*": + version "0.7.4" + resolved "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.4.tgz#26dedf791701abc0e36b5b79a5722f40e455f87b" + integrity sha512-oTGtMzZZAVuEjTwCjIh8T8FrC8n/uwy+PG0yTvQcdZ7etoel7C7/3MSd7qrukENTgQtotG7gvBlBojuVs7X5rw== + dependencies: + "@types/connect" "*" + "@types/express" "*" + "@types/keygrip" "*" + "@types/node" "*" + +"@types/cors@^2.8.4", "@types/cors@^2.8.6": version "2.8.6" resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.6.tgz#cfaab33c49c15b1ded32f235111ce9123009bd02" integrity sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg== @@ -3533,6 +3756,23 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/express@4.17.4": + version "4.17.4" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.4.tgz#e78bf09f3f530889575f4da8a94cd45384520aac" + integrity sha512-DO1L53rGqIDUEvOjJKmbMEQ5Z+BM2cIEPy/eV3En+s166Gz+FeuzRerxcab757u/U4v4XF4RYrZPmqKa+aY/2w== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "*" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/fs-capacitor@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz#17113e25817f584f58100fb7a08eed288b81956e" + integrity sha512-FKVPOCFbhCvZxpVAMhdBdTfVfXUpsh15wFHgqOKxh9N9vzWZVuWCSijZ5T4U34XYNnuj2oduh6xcs1i+LPI+BQ== + dependencies: + "@types/node" "*" + "@types/fs-extra@^9.0.1": version "9.0.1" resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.1.tgz#91c8fc4c51f6d5dbe44c2ca9ab09310bd00c7918" @@ -3566,6 +3806,16 @@ dependencies: "@types/node" "*" +"@types/graphql-upload@^8.0.0": + version "8.0.3" + resolved "https://registry.npmjs.org/@types/graphql-upload/-/graphql-upload-8.0.3.tgz#b371edb5f305a2a1f7b7843a890a2a7adc55c3ec" + integrity sha512-hmLg9pCU/GmxBscg8GCr1vmSoEmbItNNxdD5YH2TJkXm//8atjwuprB+xJBK714JG1dkxbbhp5RHX+Pz1KsCMA== + dependencies: + "@types/express" "*" + "@types/fs-capacitor" "*" + "@types/koa" "*" + graphql "^14.5.3" + "@types/helmet@^0.0.47": version "0.0.47" resolved "https://registry.npmjs.org/@types/helmet/-/helmet-0.0.47.tgz#ec5161541b649142205b7c558bca14801c5ce129" @@ -3601,6 +3851,11 @@ "@types/tapable" "*" "@types/webpack" "*" +"@types/http-assert@*": + version "1.5.1" + resolved "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b" + integrity sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ== + "@types/http-errors@^1.6.3": version "1.6.3" resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.6.3.tgz#619a55768eab98299e8f76747339f3373f134e69" @@ -3695,11 +3950,41 @@ resolved "https://registry.npmjs.org/@types/jwt-decode/-/jwt-decode-2.2.1.tgz#afdf5c527fcfccbd4009b5fd02d1e18241f2d2f2" integrity sha512-aWw2YTtAdT7CskFyxEX2K21/zSDStuf/ikI3yBqmwpwJF0pS+/IX5DWv+1UFffZIbruP6cnT9/LAJV1gFwAT1A== +"@types/keygrip@*": + version "1.0.2" + resolved "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72" + integrity sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw== + +"@types/koa-compose@*": + version "3.2.5" + resolved "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz#85eb2e80ac50be95f37ccf8c407c09bbe3468e9d" + integrity sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ== + dependencies: + "@types/koa" "*" + +"@types/koa@*": + version "2.11.3" + resolved "https://registry.npmjs.org/@types/koa/-/koa-2.11.3.tgz#540ece376581b12beadf9a417dd1731bc31c16ce" + integrity sha512-ABxVkrNWa4O/Jp24EYI/hRNqEVRlhB9g09p48neQp4m3xL1TJtdWk2NyNQSMCU45ejeELMQZBYyfstyVvO2H3Q== + dependencies: + "@types/accepts" "*" + "@types/content-disposition" "*" + "@types/cookies" "*" + "@types/http-assert" "*" + "@types/keygrip" "*" + "@types/koa-compose" "*" + "@types/node" "*" + "@types/lodash@^4.14.151": version "4.14.155" resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.155.tgz#e2b4514f46a261fd11542e47519c20ebce7bc23a" integrity sha512-vEcX7S7aPhsBCivxMwAANQburHBtfN9RdyXFk84IJmu2Z4Hkg1tOFgaslRiEqqvoLtbCBi6ika1EMspE+NZ9Lg== +"@types/long@^4.0.0": + version "4.0.1" + resolved "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" + integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== + "@types/mime@*": version "2.0.1" resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" @@ -3731,7 +4016,7 @@ dependencies: "@types/node" "*" -"@types/node-fetch@^2.5.7": +"@types/node-fetch@2.5.7", "@types/node-fetch@^2.5.7": version "2.5.7" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== @@ -3744,6 +4029,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349" integrity sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg== +"@types/node@^10.1.0": + version "10.17.27" + resolved "https://registry.npmjs.org/@types/node/-/node-10.17.27.tgz#391cb391c75646c8ad2a7b6ed3bbcee52d1bdf19" + integrity sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg== + "@types/node@^12.0.0": version "12.12.30" resolved "https://registry.npmjs.org/@types/node/-/node-12.12.30.tgz#3501e6f09b954de9c404671cefdbcc5d9d7c45f6" @@ -4161,11 +4451,25 @@ "@types/webpack-sources" "*" source-map "^0.6.0" +"@types/websocket@1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.1.tgz#039272c196c2c0e4868a0d8a1a27bbb86e9e9138" + integrity sha512-f5WLMpezwVxCLm1xQe/kdPpQIOmL0TXYx2O15VYfYzc7hTIdxiOoOvez+McSIw3b7z/1zGovew9YSL7+h4h7/Q== + dependencies: + "@types/node" "*" + "@types/whatwg-streams@^0.0.7": version "0.0.7" resolved "https://registry.npmjs.org/@types/whatwg-streams/-/whatwg-streams-0.0.7.tgz#28bfe73dc850562296367249c4b32a50db81e9d3" integrity sha512-6sDiSEP6DWcY2ZolsJ2s39ZmsoGQ7KVwBDI3sESQsEm9P2dHTcqnDIHRZFRNtLCzWp7hCFGqYbw5GyfpQnJ01A== +"@types/ws@^7.0.0": + version "7.2.6" + resolved "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz#516cbfb818310f87b43940460e065eb912a4178d" + integrity sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ== + dependencies: + "@types/node" "*" + "@types/yaml@^1.9.7": version "1.9.7" resolved "https://registry.npmjs.org/@types/yaml/-/yaml-1.9.7.tgz#2331f36e0aac91311a63d33eb026c21687729679" @@ -4390,6 +4694,13 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" +"@wry/equality@^0.1.2": + version "0.1.11" + resolved "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790" + integrity sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA== + dependencies: + tslib "^1.9.3" + "@xobotyi/scrollbar-width@1.9.5": version "1.9.5" resolved "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" @@ -4432,7 +4743,7 @@ abbrev@1: resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: +accepts@^1.3.5, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -4517,7 +4828,7 @@ agentkeepalive@^3.4.1: dependencies: humanize-ms "^1.2.1" -aggregate-error@^3.0.0: +aggregate-error@3.0.1, aggregate-error@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== @@ -4675,6 +4986,187 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +apollo-cache-control@^0.11.1: + version "0.11.1" + resolved "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.11.1.tgz#3bce0924ae7322a8b9f7ca1e2fb036d1fc9f1df5" + integrity sha512-6iHa8TkcKt4rx5SKRzDNjUIpCQX+7/FlZwD7vRh9JDnM4VH8SWhpj8fUR3CiEY8Kuc4ChXnOY8bCcMju5KPnIQ== + dependencies: + apollo-server-env "^2.4.5" + apollo-server-plugin-base "^0.9.1" + +apollo-datasource@^0.7.2: + version "0.7.2" + resolved "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.7.2.tgz#1662ee93453a9b89af6f73ce561bde46b41ebf31" + integrity sha512-ibnW+s4BMp4K2AgzLEtvzkjg7dJgCaw9M5b5N0YKNmeRZRnl/I/qBTQae648FsRKgMwTbRQIvBhQ0URUFAqFOw== + dependencies: + apollo-server-caching "^0.5.2" + apollo-server-env "^2.4.5" + +apollo-engine-reporting-protobuf@^0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.2.tgz#b01812508a1c583328a8dc603769bc63b8895e7e" + integrity sha512-4wm9FR3B7UvJxcK/69rOiS5CAJPEYKufeRWb257ZLfX7NGFTMqvbc1hu4q8Ch7swB26rTpkzfsftLED9DqH9qg== + dependencies: + "@apollo/protobufjs" "^1.0.3" + +apollo-engine-reporting@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-2.3.0.tgz#3bb59f81aaf6b967ed098896a4a60a053b4eed5a" + integrity sha512-SbcPLFuUZcRqDEZ6mSs8uHM9Ftr8yyt2IEu0JA8c3LNBmYXSLM7MHqFe80SVcosYSTBgtMz8mLJO8orhYoSYZw== + dependencies: + apollo-engine-reporting-protobuf "^0.5.2" + apollo-graphql "^0.5.0" + apollo-server-caching "^0.5.2" + apollo-server-env "^2.4.5" + apollo-server-errors "^2.4.2" + apollo-server-plugin-base "^0.9.1" + apollo-server-types "^0.5.1" + async-retry "^1.2.1" + uuid "^8.0.0" + +apollo-env@^0.6.5: + version "0.6.5" + resolved "https://registry.npmjs.org/apollo-env/-/apollo-env-0.6.5.tgz#5a36e699d39e2356381f7203493187260fded9f3" + integrity sha512-jeBUVsGymeTHYWp3me0R2CZRZrFeuSZeICZHCeRflHTfnQtlmbSXdy5E0pOyRM9CU4JfQkKDC98S1YglQj7Bzg== + dependencies: + "@types/node-fetch" "2.5.7" + core-js "^3.0.1" + node-fetch "^2.2.0" + sha.js "^2.4.11" + +apollo-graphql@^0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.5.0.tgz#7e9152093211b58352aa6504d8d39ec7241d6872" + integrity sha512-YSdF/BKPbsnQpxWpmCE53pBJX44aaoif31Y22I/qKpB6ZSGzYijV5YBoCL5Q15H2oA/v/02Oazh9lbp4ek3eig== + dependencies: + apollo-env "^0.6.5" + lodash.sortby "^4.7.0" + +apollo-link@^1.2.14: + version "1.2.14" + resolved "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.14.tgz#3feda4b47f9ebba7f4160bef8b977ba725b684d9" + integrity sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg== + dependencies: + apollo-utilities "^1.3.0" + ts-invariant "^0.4.0" + tslib "^1.9.3" + zen-observable-ts "^0.8.21" + +apollo-server-caching@^0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.2.tgz#bef5d5e0d48473a454927a66b7bb947a0b6eb13e" + integrity sha512-HUcP3TlgRsuGgeTOn8QMbkdx0hLPXyEJehZIPrcof0ATz7j7aTPA4at7gaiFHCo8gk07DaWYGB3PFgjboXRcWQ== + dependencies: + lru-cache "^5.0.0" + +apollo-server-core@^2.16.0: + version "2.16.0" + resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.16.0.tgz#56b367db49f97b7da03b29cef89b63d9ed14ee0c" + integrity sha512-mnvg2cPvsQtjFXIqIhEAbPqGyiSXDSbiBgNQ8rY8g7r2eRMhHKZePqGF03gP1/w87yVaSDRAZBDk6o+jiBXjVQ== + dependencies: + "@apollographql/apollo-tools" "^0.4.3" + "@apollographql/graphql-playground-html" "1.6.26" + "@types/graphql-upload" "^8.0.0" + "@types/ws" "^7.0.0" + apollo-cache-control "^0.11.1" + apollo-datasource "^0.7.2" + apollo-engine-reporting "^2.3.0" + apollo-server-caching "^0.5.2" + apollo-server-env "^2.4.5" + apollo-server-errors "^2.4.2" + apollo-server-plugin-base "^0.9.1" + apollo-server-types "^0.5.1" + apollo-tracing "^0.11.1" + fast-json-stable-stringify "^2.0.0" + graphql-extensions "^0.12.4" + graphql-tag "^2.9.2" + graphql-tools "^4.0.0" + graphql-upload "^8.0.2" + loglevel "^1.6.7" + sha.js "^2.4.11" + subscriptions-transport-ws "^0.9.11" + ws "^6.0.0" + +apollo-server-env@^2.4.5: + version "2.4.5" + resolved "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz#73730b4f0439094a2272a9d0caa4079d4b661d5f" + integrity sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA== + dependencies: + node-fetch "^2.1.2" + util.promisify "^1.0.0" + +apollo-server-errors@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.4.2.tgz#1128738a1d14da989f58420896d70524784eabe5" + integrity sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ== + +apollo-server-express@^2.16.0: + version "2.16.0" + resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.16.0.tgz#3474a7f7eb868a2a847a364839147f8d7f26454a" + integrity sha512-mBIvKcF8gApj7wbmqe0A4Tsy+Pw66mI6cmtD912bG59KhUBveSCZ21dDlRSvnXUyK+GOo2ItwcUEtmks+Z2Pqw== + dependencies: + "@apollographql/graphql-playground-html" "1.6.26" + "@types/accepts" "^1.3.5" + "@types/body-parser" "1.19.0" + "@types/cors" "^2.8.4" + "@types/express" "4.17.4" + accepts "^1.3.5" + apollo-server-core "^2.16.0" + apollo-server-types "^0.5.1" + body-parser "^1.18.3" + cors "^2.8.4" + express "^4.17.1" + graphql-subscriptions "^1.0.0" + graphql-tools "^4.0.0" + parseurl "^1.3.2" + subscriptions-transport-ws "^0.9.16" + type-is "^1.6.16" + +apollo-server-plugin-base@^0.9.1: + version "0.9.1" + resolved "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.9.1.tgz#a62ae9ab4e89790fd4cc5d123bb616da34e8e5fb" + integrity sha512-kvrX4Z3FdpjrZdHkyl5iY2A1Wvp4b6KQp00DeZqss7GyyKNUBKr80/7RQgBLEw7EWM7WB19j459xM/TjvW0FKQ== + dependencies: + apollo-server-types "^0.5.1" + +apollo-server-types@^0.5.1: + version "0.5.1" + resolved "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.1.tgz#091c09652894d6532db9ba873574443adabf85b9" + integrity sha512-my2cPw+DAb2qVnIuBcsRKGyS28uIc2vjFxa1NpRoJZe9gK0BWUBk7wzXnIzWy3HZ5Er11e/40MPTUesNfMYNVA== + dependencies: + apollo-engine-reporting-protobuf "^0.5.2" + apollo-server-caching "^0.5.2" + apollo-server-env "^2.4.5" + +apollo-server@^2.16.0: + version "2.16.0" + resolved "https://registry.npmjs.org/apollo-server/-/apollo-server-2.16.0.tgz#fa30e29b78e8cb70b2c81d0f7b96953beb3d4baf" + integrity sha512-zbEe0FSqatqE6bmIfq/o1/Hsqc596ZOwZM/L8Ttwa4ucQ1ybqf1ZejSYu6ehFEj1G6rOBY1ttVKkIllcErK4GQ== + dependencies: + apollo-server-core "^2.16.0" + apollo-server-express "^2.16.0" + express "^4.0.0" + graphql-subscriptions "^1.0.0" + graphql-tools "^4.0.0" + +apollo-tracing@^0.11.1: + version "0.11.1" + resolved "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.11.1.tgz#3e3a4ce4b21e57dcc57b10bbd539db243b752606" + integrity sha512-l7g+uILw7v32GA46IRXIx5XXbZhFI96BhSqrGK9yyvfq+NMcvVZrj3kIhRImPGhAjMdV+5biA/jztabElAbDjg== + dependencies: + apollo-server-env "^2.4.5" + apollo-server-plugin-base "^0.9.1" + +apollo-utilities@^1.0.1, apollo-utilities@^1.3.0: + version "1.3.4" + resolved "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.4.tgz#6129e438e8be201b6c55b0f13ce49d2c7175c9cf" + integrity sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig== + dependencies: + "@wry/equality" "^0.1.2" + fast-json-stable-stringify "^2.0.0" + ts-invariant "^0.4.0" + tslib "^1.10.0" + app-root-dir@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118" @@ -4923,6 +5415,13 @@ async-limiter@~1.0.0: resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-retry@^1.2.1: + version "1.3.1" + resolved "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55" + integrity sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA== + dependencies: + retry "0.12.0" + async@^2.6.1, async@^2.6.2: version "2.6.3" resolved "https://registry.npmjs.org/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -5320,6 +5819,11 @@ babel-runtime@6.26.0, babel-runtime@^6.26.0: core-js "^2.4.0" regenerator-runtime "^0.11.0" +backo2@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= + bail@^1.0.0: version "1.0.5" resolved "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" @@ -5441,7 +5945,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -body-parser@1.19.0, body-parser@^1.19.0: +body-parser@1.19.0, body-parser@^1.18.3, body-parser@^1.19.0: version "1.19.0" resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== @@ -5706,6 +6210,13 @@ builtins@^1.0.3: resolved "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= +busboy@^0.3.1: + version "0.3.1" + resolved "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" + integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw== + dependencies: + dicer "0.3.0" + byline@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" @@ -5833,7 +6344,7 @@ callsites@^3.0.0: resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@^4.1.1: +camel-case@4.1.1, camel-case@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== @@ -6370,7 +6881,7 @@ commander@4.1.1, commander@^4.0.0, commander@^4.0.1, commander@^4.1.1: resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.3: +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@~2.20.3: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -6729,7 +7240,7 @@ corejs-upgrade-webpack-plugin@^2.2.0: resolve-from "^5.0.0" webpack "^4.38.0" -cors@^2.8.5: +cors@^2.8.4, cors@^2.8.5: version "2.8.5" resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== @@ -6737,6 +7248,17 @@ cors@^2.8.5: object-assign "^4" vary "^1" +cosmiconfig@6.0.0, cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + cosmiconfig@^5.0.0, cosmiconfig@^5.1.0, cosmiconfig@^5.2.1: version "5.2.1" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" @@ -6747,17 +7269,6 @@ cosmiconfig@^5.0.0, cosmiconfig@^5.1.0, cosmiconfig@^5.2.1: js-yaml "^3.13.1" parse-json "^4.0.0" -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.7.2" - create-ecdh@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" @@ -6804,6 +7315,13 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" +cross-fetch@3.0.5: + version "3.0.5" + resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.5.tgz#2739d2981892e7ab488a7ad03b92df2816e03f4c" + integrity sha512-FFLcLtraisj5eteosnX1gf01qYDCOc4fDy0+euOt8Kn9YBY2NtXL/pCoYPavw24NIQkQqm5ZOLsGD5Zzj0gyew== + dependencies: + node-fetch "2.6.0" + cross-fetch@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz#7bef7020207e684a7638ef5f2f698e24d9eb283c" @@ -7010,6 +7528,11 @@ cssesc@^3.0.0: resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssfilter@0.0.10: + version "0.0.10" + resolved "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" + integrity sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4= + cssnano-preset-default@^4.0.7: version "4.0.7" resolved "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" @@ -7193,6 +7716,14 @@ d3-timer@1: resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + damerau-levenshtein@^1.0.4: version "1.0.6" resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" @@ -7466,6 +7997,11 @@ depd@~1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +deprecated-decorator@^0.1.6: + version "0.1.6" + resolved "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" + integrity sha1-AJZjF7ehL+kvPMgx91g68ym4bDc= + deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" @@ -7542,6 +8078,13 @@ diagnostics@^1.1.1: enabled "1.0.x" kuler "1.0.x" +dicer@0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" + integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== + dependencies: + streamsearch "0.1.2" + diff-sequences@^25.2.6: version "25.2.6" resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" @@ -8055,11 +8598,29 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.53" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + es5-shim@^4.5.13: version "4.5.13" resolved "https://registry.npmjs.org/es5-shim/-/es5-shim-4.5.13.tgz#5d88062de049f8969f83783f4a4884395f21d28b" integrity sha512-xi6hh6gsvDE0MaW4Vp1lgNEBpVcCXRWfPXj5egDvtgLz4L9MEvNwYEMdJH+JJinWkwa8c3c3o5HduV7dB/e1Hw== +es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + es6-promise@^4.0.3: version "4.2.8" resolved "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -8077,6 +8638,14 @@ es6-shim@^0.35.5: resolved "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.5.tgz#46f59dc0a84a1c5029e8ff1166ca0a902077a9ab" integrity sha512-E9kK/bjtCQRpN1K28Xh4BlmP8egvZBGJJ+9GtnzOwt7mdqtrjHFuVGr7QJfdjBIKqrlU5duPf3pCBoDrkjVYFg== +es6-symbol@^3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + esbuild@0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.6.3.tgz#a957e22f2503c745793514388110f9b55b3a6f83" @@ -8155,6 +8724,16 @@ eslint-plugin-cypress@^2.10.3: dependencies: globals "^11.12.0" +eslint-plugin-graphql@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/eslint-plugin-graphql/-/eslint-plugin-graphql-4.0.0.tgz#d238ff2baee4d632cfcbe787a7a70a1f50428358" + integrity sha512-d5tQm24YkVvCEk29ZR5ScsgXqAGCjKlMS8lx3mS7FS/EKsWbkvXQImpvic03EpMIvNTBW5e+2xnHzXB/VHNZJw== + dependencies: + "@babel/runtime" "^7.10.0" + graphql-config "^3.0.2" + lodash.flatten "^4.4.0" + lodash.without "^4.4.0" + eslint-plugin-import@^2.20.2, eslint-plugin-import@^2.22.0: version "2.22.0" resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" @@ -8541,7 +9120,7 @@ express-promise-router@^3.0.3: lodash.flattendeep "^4.0.0" methods "^1.0.0" -express@^4.17.0, express@^4.17.1: +express@^4.0.0, express@^4.17.0, express@^4.17.1: version "4.17.1" resolved "https://registry.npmjs.org/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== @@ -8577,6 +9156,13 @@ express@^4.17.0, express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -9143,6 +9729,11 @@ from@~0: resolved "https://registry.npmjs.org/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= +fs-capacitor@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-2.0.4.tgz#5a22e72d40ae5078b4fe64fe4d08c0d3fc88ad3c" + integrity sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA== + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -9157,6 +9748,16 @@ fs-extra@8.1.0, fs-extra@^8.0.1, fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@9.0.1, fs-extra@^9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + fs-extra@^0.30.0: version "0.30.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" @@ -9187,16 +9788,6 @@ fs-extra@^9.0.0: jsonfile "^6.0.1" universalify "^1.0.0" -fs-extra@^9.0.1: - version "9.0.1" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" - integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^1.0.0" - fs-minipass@^1.2.5: version "1.2.7" resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" @@ -9552,6 +10143,18 @@ globalthis@^1.0.0: dependencies: define-properties "^1.1.3" +globby@11.0.1, globby@^11.0.0: + version "11.0.1" + resolved "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@8.0.2: version "8.0.2" resolved "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" @@ -9579,18 +10182,6 @@ globby@^10.0.1: merge2 "^1.2.3" slash "^3.0.0" -globby@^11.0.0: - version "11.0.1" - resolved "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" - integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - globby@^6.1.0: version "6.1.0" resolved "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -9678,6 +10269,31 @@ graphiql@^1.0.0-alpha.10: regenerator-runtime "^0.13.5" theme-ui "^0.3.1" +graphql-config@^3.0.2: + version "3.0.3" + resolved "https://registry.npmjs.org/graphql-config/-/graphql-config-3.0.3.tgz#58907c65ed7d6e04132321450b60e57863ea9a5f" + integrity sha512-MBY0wEjvcgJtZUyoqpPvOE1e5qPI0hJaa1gKTqjonSFiCsNHX2lykNjpOPcodmAgH1V06ELxhGnm9kcVzqvi/g== + dependencies: + "@graphql-tools/graphql-file-loader" "^6.0.0" + "@graphql-tools/json-file-loader" "^6.0.0" + "@graphql-tools/load" "^6.0.0" + "@graphql-tools/merge" "^6.0.0" + "@graphql-tools/url-loader" "^6.0.0" + "@graphql-tools/utils" "^6.0.0" + cosmiconfig "6.0.0" + minimatch "3.0.4" + string-env-interpolation "1.0.1" + tslib "^2.0.0" + +graphql-extensions@^0.12.4: + version "0.12.4" + resolved "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.4.tgz#c0aa49a20f983a2da641526d1e505996bd2b4188" + integrity sha512-GnR4LiWk3s2bGOqIh6V1JgnSXw2RCH4NOgbCFEWvB6JqWHXTlXnLZ8bRSkCiD4pltv7RHUPWqN/sGh8R6Ae/ag== + dependencies: + "@apollographql/apollo-tools" "^0.4.3" + apollo-server-env "^2.4.5" + apollo-server-types "^0.5.1" + graphql-language-service-interface@^2.4.0: version "2.4.0" resolved "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.4.0.tgz#4e2e63242c76197c4f56c61122db68187ea566b3" @@ -9713,11 +10329,56 @@ graphql-language-service@^3.0.0: graphql-language-service-interface "^2.4.0" graphql-language-service-types "^1.6.0" +graphql-subscriptions@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz#5f2fa4233eda44cf7570526adfcf3c16937aef11" + integrity sha512-6WzlBFC0lWmXJbIVE8OgFgXIP4RJi3OQgTPa0DVMsDXdpRDjTsM1K9wfl5HSYX7R87QAGlvcv2Y4BIZa/ItonA== + dependencies: + iterall "^1.2.1" + +graphql-tag@^2.9.2: + version "2.10.4" + resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.4.tgz#2f301a98219be8b178a6453bb7e33b79b66d8f83" + integrity sha512-O7vG5BT3w6Sotc26ybcvLKNTdfr4GfsIVMD+LdYqXCeJIYPRyp8BIsDOUtxw7S1PYvRw5vH3278J2EDezR6mfA== + +graphql-tools@^4.0.0: + version "4.0.8" + resolved "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz#e7fb9f0d43408fb0878ba66b522ce871bafe9d30" + integrity sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg== + dependencies: + apollo-link "^1.2.14" + apollo-utilities "^1.0.1" + deprecated-decorator "^0.1.6" + iterall "^1.1.3" + uuid "^3.1.0" + +graphql-upload@^8.0.2: + version "8.1.0" + resolved "https://registry.npmjs.org/graphql-upload/-/graphql-upload-8.1.0.tgz#6d0ab662db5677a68bfb1f2c870ab2544c14939a" + integrity sha512-U2OiDI5VxYmzRKw0Z2dmfk0zkqMRaecH9Smh1U277gVgVe9Qn+18xqf4skwr4YJszGIh7iQDZ57+5ygOK9sM/Q== + dependencies: + busboy "^0.3.1" + fs-capacitor "^2.0.4" + http-errors "^1.7.3" + object-path "^0.11.4" + graphql@15.1.0, graphql@^15.0.0: version "15.1.0" resolved "https://registry.npmjs.org/graphql/-/graphql-15.1.0.tgz#b93e28de805294ec08e1630d901db550cb8960a1" integrity sha512-0TVyfOlCGhv/DBczQkJmwXOK6fjWkjzY3Pt7wY8i0gcYXq8aogG3weCsg48m72lywKSeOqedEHvVPOvZvSD51Q== +graphql@^14.5.3: + version "14.7.0" + resolved "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz#7fa79a80a69be4a31c27dda824dc04dac2035a72" + integrity sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA== + dependencies: + iterall "^1.2.2" + +graphql@^15.3.0: + version "15.3.0" + resolved "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" + integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== + growly@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -10358,6 +11019,13 @@ import-fresh@^3.0.0, import-fresh@^3.1.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-from@3.0.0, import-from@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" + integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== + dependencies: + resolve-from "^5.0.0" + import-from@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" @@ -10365,13 +11033,6 @@ import-from@^2.1.0: dependencies: resolve-from "^3.0.0" -import-from@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" - integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== - dependencies: - resolve-from "^5.0.0" - import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -10817,6 +11478,13 @@ is-generator-fn@^2.0.0: resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-glob@4.0.1, is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + is-glob@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -10831,13 +11499,6 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - is-hexadecimal@^1.0.0: version "1.0.4" resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" @@ -11180,6 +11841,11 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2: + version "1.3.0" + resolved "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" + integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== + iterate-iterator@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6" @@ -12318,6 +12984,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.flattendeep@^4.0.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" @@ -12378,6 +13049,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= +lodash.without@^4.4.0: + version "4.4.0" + resolved "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" + integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= + lodash@4.17.15: version "4.17.15" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -12439,11 +13115,16 @@ logform@^2.1.1: ms "^2.1.1" triple-beam "^1.3.0" -loglevel@^1.6.8: +loglevel@^1.6.7, loglevel@^1.6.8: version "1.6.8" resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== +long@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -12492,7 +13173,7 @@ lowlight@~1.11.0: fault "^1.0.2" highlight.js "~9.13.0" -lru-cache@^5.1.1: +lru-cache@^5.0.0, lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== @@ -13255,6 +13936,11 @@ neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -13289,7 +13975,7 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@2.6.0, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0: +node-fetch@2.6.0, node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0: version "2.6.0" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== @@ -13688,6 +14374,11 @@ object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== +object-path@^0.11.4: + version "0.11.4" + resolved "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949" + integrity sha1-NwrnUvvzfePqcKhhwju6iRVpGUk= + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -13949,6 +14640,13 @@ p-finally@^2.0.0: resolved "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== +p-limit@3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" + integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== + dependencies: + p-try "^2.0.0" + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -14206,7 +14904,7 @@ parse5@5.1.1: resolved "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@^1.3.2, parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -16341,6 +17039,11 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1: expand-tilde "^2.0.0" global-modules "^1.0.0" +resolve-from@5.0.0, resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -16351,11 +17054,6 @@ resolve-from@^4.0.0: resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -16411,16 +17109,16 @@ ret@~0.1.10: resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +retry@0.12.0, retry@^0.12.0: + version "0.12.0" + resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + retry@^0.10.0: version "0.10.1" resolved "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - reusify@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -16874,7 +17572,7 @@ setprototypeof@1.1.1: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== -sha.js@^2.4.0, sha.js@^2.4.8: +sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== @@ -17496,7 +18194,7 @@ stream-shift@^1.0.0: resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -streamsearch@~0.1.2: +streamsearch@0.1.2, streamsearch@~0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= @@ -17511,6 +18209,11 @@ string-argv@0.3.1: resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== +string-env-interpolation@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz#ad4397ae4ac53fe6c91d1402ad6f6a52862c7152" + integrity sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg== + string-hash@^1.1.1: version "1.1.3" resolved "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" @@ -17773,6 +18476,17 @@ stylis@3.5.0: resolved "https://registry.npmjs.org/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1" integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw== +subscriptions-transport-ws@0.9.17, subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.16: + version "0.9.17" + resolved "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.17.tgz#e30e40f0caae0d2781903c01a8cb51b6e2682098" + integrity sha512-hNHi2N80PBz4T0V0QhnnsMGvG3XDFDS9mS6BhZ3R12T6EBywC8d/uJscsga0cVO4DKtXCkCRrWm2sOYrbOdhEA== + dependencies: + backo2 "^1.0.2" + eventemitter3 "^3.1.0" + iterall "^1.2.1" + symbol-observable "^1.0.4" + ws "^5.2.0" + sucrase@^3.14.1: version "3.15.0" resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.15.0.tgz#78596a78be7264a65b52ed8d873883413ef0220c" @@ -17881,7 +18595,7 @@ swr@^0.2.2: dependencies: fast-deep-equal "2.0.1" -symbol-observable@^1.1.0, symbol-observable@^1.2.0: +symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -18392,6 +19106,13 @@ ts-interface-checker@^0.1.9: resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.10.tgz#b68a49e37e90a05797e590f08494dd528bf383cf" integrity sha512-UJYuKET7ez7ry0CnvfY6fPIUIZDw+UI3qvTUQeS2MyI4TgEeWAUBqy185LeaHcdJ9zG2dgFpPJU/AecXU0Afug== +ts-invariant@^0.4.0: + version "0.4.4" + resolved "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz#97a523518688f93aafad01b0e80eb803eb2abd86" + integrity sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA== + dependencies: + tslib "^1.9.3" + ts-jest@^26.0.0: version "26.1.1" resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-26.1.1.tgz#b98569b8a4d4025d966b3d40c81986dd1c510f8d" @@ -18455,6 +19176,11 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== +tslib@^2.0.0, tslib@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3" + integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -18518,7 +19244,7 @@ type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: +type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -18526,6 +19252,16 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +type@^1.0.1: + version "1.2.0" + resolved "https://registry.npmjs.org/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" + integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== + typed-styles@^0.0.7: version "0.0.7" resolved "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" @@ -18737,6 +19473,13 @@ universalify@^1.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== +unixify@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz#3a641c8c2ffbce4da683a5c70f03a462940c2090" + integrity sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA= + dependencies: + normalize-path "^2.1.1" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -18880,7 +19623,7 @@ util.promisify@1.0.0: define-properties "^1.1.2" object.getownpropertydescriptors "^2.0.3" -util.promisify@~1.0.0: +util.promisify@^1.0.0, util.promisify@~1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== @@ -18914,7 +19657,7 @@ utils-merge@1.0.1, utils-merge@1.x.x: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: +uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -18955,6 +19698,11 @@ v8flags@^3.1.3: dependencies: homedir-polyfill "^1.0.1" +valid-url@1.0.9: + version "1.0.9" + resolved "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" + integrity sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA= + validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3: version "3.0.4" resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -19267,6 +20015,17 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +websocket@1.0.31: + version "1.0.31" + resolved "https://registry.npmjs.org/websocket/-/websocket-1.0.31.tgz#e5d0f16c3340ed87670e489ecae6144c79358730" + integrity sha512-VAouplvGKPiKFDTeCCO65vYHsyay8DqoBSlzIO3fayrfOgU94lQN5a1uWVnFrMLceTJw/+fQXR5PGbUVRaHshQ== + dependencies: + debug "^2.2.0" + es5-ext "^0.10.50" + nan "^2.14.0" + typedarray-to-buffer "^3.1.5" + yaeti "^0.0.6" + whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -19504,7 +20263,7 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" -ws@^6.1.2, ws@^6.2.1: +ws@^6.0.0, ws@^6.1.2, ws@^6.2.1: version "6.2.1" resolved "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== @@ -19594,6 +20353,14 @@ xregexp@^4.3.0: dependencies: "@babel/runtime-corejs3" "^7.8.3" +xss@^1.0.6: + version "1.0.7" + resolved "https://registry.npmjs.org/xss/-/xss-1.0.7.tgz#a554cbd5e909324bd6893fb47fff441ad54e2a95" + integrity sha512-A9v7tblGvxu8TWXQC9rlpW96a+LN1lyw6wyhpTmmGW+FwRMactchBR3ROKSi33UPCUcUHSu8s9YP6F+K3Mw//w== + dependencies: + commander "^2.20.3" + cssfilter "0.0.10" + xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -19604,6 +20371,11 @@ y18n@^4.0.0: resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= + yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" @@ -19739,7 +20511,15 @@ yup@^0.29.1: synchronous-promise "^2.0.10" toposort "^2.0.2" -zen-observable@^0.8.15: +zen-observable-ts@^0.8.21: + version "0.8.21" + resolved "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d" + integrity sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg== + dependencies: + tslib "^1.9.3" + zen-observable "^0.8.0" + +zen-observable@^0.8.0, zen-observable@^0.8.15: version "0.8.15" resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== From 69276161507a64f5624399e75d6f29e620de4f01 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 23 Jul 2020 20:58:57 +0200 Subject: [PATCH 44/78] chore(graphql): catalog should be required --- plugins/graphql/src/schema.gql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/graphql/src/schema.gql b/plugins/graphql/src/schema.gql index a03bdbc317..5568182f7f 100644 --- a/plugins/graphql/src/schema.gql +++ b/plugins/graphql/src/schema.gql @@ -7,5 +7,5 @@ type CatalogQuery { } type Query { - catalog: CatalogQuery + catalog: CatalogQuery! } From fa97d2fbfb23a4ba42e272269eff1a31fb22b1a3 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Thu, 23 Jul 2020 20:51:34 -0500 Subject: [PATCH 45/78] failing yaml import --- plugins/welcome/src/components/WelcomePage/WelcomePage.tsx | 3 ++- plugins/welcome/src/components/WelcomePage/test.yaml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 plugins/welcome/src/components/WelcomePage/test.yaml diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx index 272297ed45..509ffb6562 100644 --- a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx +++ b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx @@ -37,12 +37,13 @@ import { useApi, configApiRef, } from '@backstage/core'; +import work from './test.yaml' const WelcomePage = () => { const appTitle = useApi(configApiRef).getOptionalString('app.title') ?? 'Backstage'; const profile = { givenName: '' }; - +console.log(work); return (
Date: Thu, 23 Jul 2020 20:54:38 -0500 Subject: [PATCH 46/78] feat(build): rollup yaml files for import tested importing yaml file to welcome plugin: yarn build with this commit the build succeeded --- packages/cli/package.json | 1 + packages/cli/src/lib/builder/config.ts | 2 ++ packages/cli/src/types.d.ts | 2 ++ yarn.lock | 16 +++++++++++++++- 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ed7ae9725b..e333cd460e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -37,6 +37,7 @@ "@rollup/plugin-commonjs": "^13.0.0", "@rollup/plugin-json": "^4.0.2", "@rollup/plugin-node-resolve": "^8.1.0", + "@rollup/plugin-yaml": "^2.1.1", "@spotify/eslint-config": "^7.0.1", "@sucrase/webpack-loader": "^2.0.0", "@svgr/plugin-jsx": "4.3.x", diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 5987290604..e62e1fc6cd 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -26,6 +26,7 @@ import imageFiles from 'rollup-plugin-image-files'; import svgr from '@svgr/rollup'; import dts from 'rollup-plugin-dts'; import json from '@rollup/plugin-json'; +import yaml from '@rollup/plugin-yaml'; import { RollupOptions, OutputOptions } from 'rollup'; import { BuildOptions, Output } from './types'; @@ -93,6 +94,7 @@ export const makeConfigs = async ( postcss(), imageFiles({ exclude: '**/*.icon.svg' }), json(), + yaml(), svgr({ include: '**/*.icon.svg', template: svgrTemplate, diff --git a/packages/cli/src/types.d.ts b/packages/cli/src/types.d.ts index 8cb5a1e7df..94136f1dbc 100644 --- a/packages/cli/src/types.d.ts +++ b/packages/cli/src/types.d.ts @@ -27,3 +27,5 @@ declare module 'rollup-plugin-image-files' { declare module '@svgr/rollup' { export default function svgr(options?: any): any; } + +declare module '@rollup/plugin-yaml'; diff --git a/yarn.lock b/yarn.lock index 6c0ff99388..182d6ddbaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2509,7 +2509,16 @@ is-module "^1.0.0" resolve "^1.14.2" -"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": +"@rollup/plugin-yaml@^2.1.1": + version "2.1.1" + resolved "https://registry.npmjs.org/@rollup/plugin-yaml/-/plugin-yaml-2.1.1.tgz#6c864adee22004eb325d53bc4ee5553828e056d8" + integrity sha512-CnGD3dbDhP+JeKFX7kJuaBOa7jVzXpl3G8lXp1hasB87cDYOz7qNWd4cFPmYbxMsT/8OCVsceIpRtQ+cj9uxBw== + dependencies: + "@rollup/pluginutils" "^3.0.1" + js-yaml "^3.13.1" + tosource "^1.0.0" + +"@rollup/pluginutils@^3.0.1", "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== @@ -18294,6 +18303,11 @@ toposort@^2.0.2: resolved "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= +tosource@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/tosource/-/tosource-1.0.0.tgz#42d88dd116618bcf00d6106dd5446f3427902ff1" + integrity sha1-QtiN0RZhi88A1hBt1URvNCeQL/E= + touch@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" From 2c5044215d6fb2dd0a9d3d8ec53b21b716c74fcf Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Thu, 23 Jul 2020 20:54:55 -0500 Subject: [PATCH 47/78] Revert "failing yaml import" This reverts commit fa97d2fbfb23a4ba42e272269eff1a31fb22b1a3. --- plugins/welcome/src/components/WelcomePage/WelcomePage.tsx | 3 +-- plugins/welcome/src/components/WelcomePage/test.yaml | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 plugins/welcome/src/components/WelcomePage/test.yaml diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx index 509ffb6562..272297ed45 100644 --- a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx +++ b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx @@ -37,13 +37,12 @@ import { useApi, configApiRef, } from '@backstage/core'; -import work from './test.yaml' const WelcomePage = () => { const appTitle = useApi(configApiRef).getOptionalString('app.title') ?? 'Backstage'; const profile = { givenName: '' }; -console.log(work); + return (
Date: Fri, 24 Jul 2020 09:54:49 +0200 Subject: [PATCH 48/78] chore(cli): Added fake test --- plugins/graphql/src/service/router.test.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 plugins/graphql/src/service/router.test.ts diff --git a/plugins/graphql/src/service/router.test.ts b/plugins/graphql/src/service/router.test.ts new file mode 100644 index 0000000000..30bcb8e25d --- /dev/null +++ b/plugins/graphql/src/service/router.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import './router'; + +describe('Router', () => { + it('should pass the test', () => { + expect(true).toBeTruthy(); + }); +}); From a852c2f1415495a16259e6285af2302827fbebaf Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 24 Jul 2020 11:38:45 +0200 Subject: [PATCH 49/78] config,config-loader: fix lagging versions --- packages/backend-common/package.json | 2 +- packages/backend/package.json | 4 ++-- packages/catalog-model/package.json | 2 +- packages/cli/package.json | 4 ++-- packages/config-loader/package.json | 4 ++-- packages/config/package.json | 2 +- packages/core-api/package.json | 4 ++-- packages/core/package.json | 2 +- packages/test-utils-core/package.json | 2 +- packages/test-utils/package.json | 2 +- plugins/auth-backend/package.json | 4 ++-- plugins/proxy-backend/package.json | 4 ++-- plugins/scaffolder-backend/package.json | 2 +- plugins/tech-radar/package.json | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 5f93574665..3c944846d1 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "compression": "^1.7.4", diff --git a/packages/backend/package.json b/packages/backend/package.json index f0072a9fe2..9dff72b71a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -20,8 +20,8 @@ "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.16", "@backstage/catalog-model": "^0.1.1-alpha.16", - "@backstage/config": "^0.1.1-alpha.13", - "@backstage/config-loader": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", + "@backstage/config-loader": "^0.1.1-alpha.16", "@backstage/plugin-auth-backend": "^0.1.1-alpha.16", "@backstage/plugin-catalog-backend": "^0.1.1-alpha.16", "@backstage/plugin-identity-backend": "^0.1.1-alpha.16", diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 14212e84e0..43ac830327 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", "@types/json-schema": "^7.0.5", "@types/yup": "^0.28.2", "json-schema": "^0.2.5", diff --git a/packages/cli/package.json b/packages/cli/package.json index e2fffb9758..186e096c3e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -29,8 +29,8 @@ "backstage-cli": "bin/backstage-cli" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", - "@backstage/config-loader": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", + "@backstage/config-loader": "^0.1.1-alpha.16", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^3.18.5", "@lerna/project": "^3.18.0", diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 25512529b8..72bfc0378b 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.1.1-alpha.13", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", "fs-extra": "^9.0.0", "yaml": "^1.9.2", "yup": "^0.29.1" diff --git a/packages/config/package.json b/packages/config/package.json index aac52ab023..a401bc9af5 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "0.1.1-alpha.13", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 0f0251f23c..38abdf9487 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", @@ -42,7 +42,7 @@ }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.16", - "@backstage/test-utils-core": "^0.1.1-alpha.13", + "@backstage/test-utils-core": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/packages/core/package.json b/packages/core/package.json index 20a947a202..4b29f22ba3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", "@backstage/core-api": "^0.1.1-alpha.16", "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index 10e5b0ea6b..390aad87d6 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils-core", "description": "Utilities to test Backstage core", - "version": "0.1.1-alpha.13", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index ef595f53ed..ed3ee31540 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -31,7 +31,7 @@ "dependencies": { "@backstage/cli": "^0.1.1-alpha.16", "@backstage/core-api": "^0.1.1-alpha.16", - "@backstage/test-utils-core": "^0.1.1-alpha.13", + "@backstage/test-utils-core": "^0.1.1-alpha.16", "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 319c74fb90..b16c1e5a4f 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -21,8 +21,8 @@ }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.16", - "@backstage/config": "^0.1.1-alpha.13", - "@backstage/config-loader": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", + "@backstage/config-loader": "^0.1.1-alpha.16", "@types/express": "^4.17.6", "body-parser": "^1.19.0", "compression": "^1.7.4", diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 2f694382b3..e80bf0e00f 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -20,8 +20,8 @@ }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.16", - "@backstage/config": "^0.1.1-alpha.13", - "@backstage/config-loader": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", + "@backstage/config-loader": "^0.1.1-alpha.16", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index fef2a3b2c3..9dee285320 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -23,7 +23,7 @@ "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.16", "@backstage/catalog-model": "^0.1.1-alpha.16", - "@backstage/config": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", "@octokit/rest": "^18.0.0", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 1b801beebd..1bcc13cd55 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/core": "^0.1.1-alpha.16", - "@backstage/test-utils-core": "^0.1.1-alpha.13", + "@backstage/test-utils-core": "^0.1.1-alpha.16", "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", From 64c0c67c8fca840a37babe9d5d2ef83199493350 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Mon, 20 Jul 2020 09:21:29 +0200 Subject: [PATCH 50/78] feat: github actions details view --- app-config.yaml | 4 + plugins/github-actions/package.json | 2 + .../src/api/GithubActionsApi.ts | 2 +- .../src/api/GithubActionsClient.ts | 4 +- .../BuildDetailsPage/BuildDetailsPage.tsx | 143 ----------- .../BuildInfoCard/BuildInfoCard.tsx | 107 -------- .../WorkflowRunDetailsPage.tsx | 228 ++++++++++++++++++ .../index.ts | 2 +- .../useWorkflowRunJobs.ts} | 11 +- .../useWorkflowRunsDetails.ts | 35 +++ .../WorkflowRunStatusIndicator.tsx} | 41 ++-- .../index.ts | 2 +- .../WorkflowRunsPage.tsx} | 15 +- .../index.ts | 2 +- .../WorkflowRunsTable.tsx} | 57 +++-- .../index.ts | 4 +- .../useWorkflowRuns.ts} | 56 +++-- .../github-actions/src/components/types.ts | 45 ++++ plugins/github-actions/src/plugin.ts | 12 +- yarn.lock | 5 + 20 files changed, 439 insertions(+), 338 deletions(-) delete mode 100644 plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx delete mode 100644 plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx create mode 100644 plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx rename plugins/github-actions/src/components/{BuildDetailsPage => WorkflowRunDetailsPage}/index.ts (89%) rename plugins/github-actions/src/components/{BuildInfoCard/index.ts => WorkflowRunDetailsPage/useWorkflowRunJobs.ts} (64%) create mode 100644 plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunsDetails.ts rename plugins/github-actions/src/components/{BuildStatusIndicator/BuildStatusIndicator.tsx => WorkflowRunStatusIndicator/WorkflowRunStatusIndicator.tsx} (67%) rename plugins/github-actions/src/components/{BuildStatusIndicator => WorkflowRunStatusIndicator}/index.ts (88%) rename plugins/github-actions/src/components/{BuildListPage/BuildListPage.tsx => WorkflowRunsPage/WorkflowRunsPage.tsx} (75%) rename plugins/github-actions/src/components/{BuildListPage => WorkflowRunsPage}/index.ts (91%) rename plugins/github-actions/src/components/{BuildListTable/BuildListTable.tsx => WorkflowRunsTable/WorkflowRunsTable.tsx} (78%) rename plugins/github-actions/src/components/{BuildListTable => WorkflowRunsTable}/index.ts (84%) rename plugins/github-actions/src/components/{BuildListTable/useBuilds.ts => WorkflowRunsTable/useWorkflowRuns.ts} (64%) create mode 100644 plugins/github-actions/src/components/types.ts diff --git a/app-config.yaml b/app-config.yaml index 9a4a14b923..7dacdc56ac 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -20,6 +20,10 @@ proxy: organization: name: Spotify +github-actions: + owner: CircleCITest3 + repo: try-ssr + techdocs: storageUrl: https://techdocs-mock-sites.storage.googleapis.com diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index ce0068c35a..ce83f44e5f 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -30,8 +30,10 @@ "@material-ui/lab": "4.0.0-alpha.45", "@octokit/rest": "^18.0.0", "@octokit/types": "^5.0.1", + "moment": "^2.27.0", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-lazylog": "^4.5.2", "react-router-dom": "6.0.0-beta.0", "react-use": "^14.2.0" }, diff --git a/plugins/github-actions/src/api/GithubActionsApi.ts b/plugins/github-actions/src/api/GithubActionsApi.ts index eedf9686f3..bb54fae703 100644 --- a/plugins/github-actions/src/api/GithubActionsApi.ts +++ b/plugins/github-actions/src/api/GithubActionsApi.ts @@ -72,5 +72,5 @@ export type GithubActionsApi = { owner: string; repo: string; runId: number; - }) => void; + }) => Promise; }; diff --git a/plugins/github-actions/src/api/GithubActionsClient.ts b/plugins/github-actions/src/api/GithubActionsClient.ts index 1606c268f4..6e6f1cb1e2 100644 --- a/plugins/github-actions/src/api/GithubActionsClient.ts +++ b/plugins/github-actions/src/api/GithubActionsClient.ts @@ -33,8 +33,8 @@ export class GithubActionsClient implements GithubActionsApi { owner: string; repo: string; runId: number; - }) { - new Octokit({ auth: token }).actions.reRunWorkflow({ + }): Promise { + return new Octokit({ auth: token }).actions.reRunWorkflow({ owner, repo, run_id: runId, diff --git a/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx b/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx deleted file mode 100644 index 55c568106c..0000000000 --- a/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Button, - LinearProgress, - makeStyles, - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableRow, - Theme, - Typography, -} from '@material-ui/core'; -import React from 'react'; -import { useParams } from 'react-router-dom'; -import { useAsync } from 'react-use'; -import { Link, useApi, githubAuthApiRef } from '@backstage/core'; -import { githubActionsApiRef } from '../../api'; - -const useStyles = makeStyles(theme => ({ - root: { - maxWidth: 720, - margin: theme.spacing(2), - }, - title: { - padding: theme.spacing(1, 0, 2, 0), - }, - table: { - padding: theme.spacing(1), - }, -})); - -export const BuildDetailsPage = () => { - const repo = 'try-ssr'; - const owner = 'CircleCITest3'; - const api = useApi(githubActionsApiRef); - const auth = useApi(githubAuthApiRef); - - const classes = useStyles(); - const { id } = useParams(); - const status = useAsync(async () => { - const token = await auth.getAccessToken(['repo', 'user']); - return api - .getWorkflowRun({ - token, - owner, - repo, - id: parseInt(id, 10), - }) - .then(data => { - return data; - }); - }, [location.search]); - - if (status.loading) { - return ; - } else if (status.error) { - return ( - - Failed to load build, {status.error.message} - - ); - } - - const details = status.value; - - return ( -
- - - - < - - - Build Details - - - - - - - Branch - - {details?.head_branch} - - - - Message - - {details?.head_commit.message} - - - - Commit ID - - {details?.head_commit.id} - - - - Status - - {details?.status} - - - - Author - - {`${details?.head_commit.author.name} (${details?.head_commit.author.email})`} - - - - Links - - - {details?.html_url && ( - - )} - - - -
-
-
- ); -}; diff --git a/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx b/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx deleted file mode 100644 index acc1b496ec..0000000000 --- a/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - LinearProgress, - makeStyles, - Table, - TableBody, - TableCell, - TableRow, - Theme, - Typography, -} from '@material-ui/core'; -import React from 'react'; -import { useAsync } from 'react-use'; -import { BuildStatusIndicator } from '../BuildStatusIndicator'; -import { githubActionsApiRef, BuildStatus } from '../../api'; -import { Link, useApi, githubAuthApiRef } from '@backstage/core'; - -const useStyles = makeStyles(theme => ({ - root: { - // height: 400, - }, - title: { - paddingBottom: theme.spacing(1), - }, -})); - -const BuildInfoCardContent = () => { - const api = useApi(githubActionsApiRef); - const auth = useApi(githubAuthApiRef); - - const status = useAsync(async () => { - const token = await auth.getAccessToken(['repo', 'user']); - return api.listWorkflowRuns({ token, owner: 'spotify', repo: 'backstage' }); - }); - - if (status.loading) { - return ; - } else if (status.error) { - return ( - - Failed to load builds, {status.error.message} - - ); - } - - // const [build] = - // status.value?.filter(({ branch }) => branch === 'master') ?? []; - - return ( - - - - - Message - - - - build message - - - - - - Commit ID - - build commit id - - - - Status - - - - - - -
- ); -}; - -export const BuildInfoCard = () => { - const classes = useStyles(); - - return ( -
- - Master Build - - -
- ); -}; diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx new file mode 100644 index 0000000000..0bf72c77fd --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx @@ -0,0 +1,228 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Button, + LinearProgress, + makeStyles, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableRow, + Theme, + Typography, + Box, + ExpansionPanelDetails, + ExpansionPanel, + ExpansionPanelSummary, + ListItem, + List, + ListItemText, + CircularProgress, +} from '@material-ui/core'; +import moment from 'moment'; + +import React from 'react'; +import { Link, useApi, configApiRef } from '@backstage/core'; +import { Job, Step, Jobs } from '../types'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { WorkflowRunStatusIndicator } from '../WorkflowRunStatusIndicator'; +import { useWorkflowRunsDetails } from './useWorkflowRunsDetails'; +import { useWorkflowRunJobs } from './useWorkflowRunJobs'; + +const useStyles = makeStyles(theme => ({ + root: { + maxWidth: 720, + margin: theme.spacing(2), + }, + title: { + padding: theme.spacing(1, 0, 2, 0), + }, + table: { + padding: theme.spacing(1), + }, + expansionPanelDetails: { + padding: 0, + }, + button: { + order: -1, + marginRight: 0, + marginLeft: '-20px', + }, +})); + +const JobsList = ({ jobs }: { jobs?: Jobs }) => { + const classes = useStyles(); + return ( + + {jobs && + jobs.total_count > 0 && + jobs.jobs.map((job: Job) => ( + + ))} + + ); +}; + +const getElapsedTime = (start: string, end: string) => { + const diff = moment(moment(end || moment()).diff(moment(start))); + const timeElapsed = diff.format('m [minutes] s [seconds]'); + return timeElapsed; +}; + +const StepView = ({ step }: { step: Step }) => { + return ( + + + + ); +}; + +const JobListItem = ({ job, className }: { job: Job; className: string }) => { + const classes = useStyles(); + return ( + + } + aria-controls={`panel-${name}-content`} + id={`panel-${name}-header`} + IconButtonProps={{ + className: classes.button, + }} + > + + {job.name} ({getElapsedTime(job.started_at, job.completed_at)}) + + + + + {job.steps.map((step: Step) => ( + + ))} + + + + ); +}; + +/** + * A component for Jobs visualization. Jobs are a property of a Workflow Run. + */ +export const WorkflowRunDetailsPage = () => { + const configApi = useApi(configApiRef); + const repo = configApi.getString('github-actions.repo'); + const owner = configApi.getString('github-actions.owner'); + const details = useWorkflowRunsDetails(repo, owner); + const jobs = useWorkflowRunJobs(details.value?.jobs_url); + + const classes = useStyles(); + + if (details.loading) { + return ; + } else if (details.error) { + return ( + + Failed to load build, {details.error.message} + + ); + } + + return ( +
+ + + + < + + + Workflow Run Details + + + + + + + Branch + + {details.value?.head_branch} + + + + Message + + {details.value?.head_commit.message} + + + + Commit ID + + {details.value?.head_commit.id} + + + + Status + + + + + + + + Author + + {`${details.value?.head_commit.author.name} (${details.value?.head_commit.author.email})`} + + + + Links + + + {details.value?.html_url && ( + + )} + + + + + Jobs + {jobs.loading ? ( + + ) : ( + + )} + + + +
+
+
+ ); +}; diff --git a/plugins/github-actions/src/components/BuildDetailsPage/index.ts b/plugins/github-actions/src/components/WorkflowRunDetailsPage/index.ts similarity index 89% rename from plugins/github-actions/src/components/BuildDetailsPage/index.ts rename to plugins/github-actions/src/components/WorkflowRunDetailsPage/index.ts index d59d0fc396..443bcacc88 100644 --- a/plugins/github-actions/src/components/BuildDetailsPage/index.ts +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { BuildDetailsPage } from './BuildDetailsPage'; +export { WorkflowRunDetailsPage } from './WorkflowRunDetailsPage'; diff --git a/plugins/github-actions/src/components/BuildInfoCard/index.ts b/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunJobs.ts similarity index 64% rename from plugins/github-actions/src/components/BuildInfoCard/index.ts rename to plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunJobs.ts index a6409e682a..c941ed6389 100644 --- a/plugins/github-actions/src/components/BuildInfoCard/index.ts +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunJobs.ts @@ -13,5 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { useAsync } from 'react-use'; +import { Jobs } from '../types'; -export { BuildInfoCard } from './BuildInfoCard'; +export const useWorkflowRunJobs = (jobsUrl?: string) => { + const jobs = useAsync(async () => { + if (jobsUrl === undefined) return []; + const data = await fetch(jobsUrl).then(d => d.json()); + return data; + }, [jobsUrl]); + return jobs; +}; diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunsDetails.ts b/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunsDetails.ts new file mode 100644 index 0000000000..088c286d94 --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunsDetails.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useApi, githubAuthApiRef } from '@backstage/core'; +import { useParams } from 'react-router-dom'; +import { useAsync } from 'react-use'; +import { githubActionsApiRef } from '../../api'; + +export const useWorkflowRunsDetails = (repo: string, owner: string) => { + const api = useApi(githubActionsApiRef); + const auth = useApi(githubAuthApiRef); + const { id } = useParams(); + const details = useAsync(async () => { + const token = await auth.getAccessToken(['repo', 'user']); + return api.getWorkflowRun({ + token, + owner, + repo, + id: parseInt(id, 10), + }); + }, [id]); + return details; +}; diff --git a/plugins/github-actions/src/components/BuildStatusIndicator/BuildStatusIndicator.tsx b/plugins/github-actions/src/components/WorkflowRunStatusIndicator/WorkflowRunStatusIndicator.tsx similarity index 67% rename from plugins/github-actions/src/components/BuildStatusIndicator/BuildStatusIndicator.tsx rename to plugins/github-actions/src/components/WorkflowRunStatusIndicator/WorkflowRunStatusIndicator.tsx index 1a198f4df2..a22c60cdcb 100644 --- a/plugins/github-actions/src/components/BuildStatusIndicator/BuildStatusIndicator.tsx +++ b/plugins/github-actions/src/components/WorkflowRunStatusIndicator/WorkflowRunStatusIndicator.tsx @@ -15,16 +15,14 @@ */ import { IconComponent } from '@backstage/core'; -import { makeStyles, Theme } from '@material-ui/core'; +import { makeStyles, Theme, Typography } from '@material-ui/core'; import ProgressIcon from '@material-ui/icons/Autorenew'; import SuccessIcon from '@material-ui/icons/CheckCircle'; -import FailureIcon from '@material-ui/icons/Error'; import UnknownIcon from '@material-ui/icons/Help'; import React from 'react'; -import { BuildStatus } from '../../api/types'; type Props = { - status?: BuildStatus; + status?: string; }; type StatusStyle = { @@ -32,43 +30,50 @@ type StatusStyle = { color: string; }; -const styles: { [key in BuildStatus]: StatusStyle } = { - [BuildStatus.Null]: { +const styles: { [key: string]: StatusStyle } = { + unknown: { icon: UnknownIcon, color: '#f49b20', }, - [BuildStatus.Success]: { + completed: { icon: SuccessIcon, color: '#1db855', }, - [BuildStatus.Failure]: { - icon: FailureIcon, - color: '#CA001B', - }, - [BuildStatus.Pending]: { + queued: { icon: UnknownIcon, color: '#5BC0DE', }, - [BuildStatus.Running]: { + in_progress: { icon: ProgressIcon, color: '#BEBEBE', }, }; +const getIconStyle = (status?: string): StatusStyle => { + return styles[status ?? 'unknown'] ?? styles.unknown; +}; + const useStyles = makeStyles({ icon: style => ({ color: style.color, }), }); -export const BuildStatusIndicator = ({ status }: Props) => { - const style = (status && styles[status]) || styles[BuildStatus.Null]; +export const WorkflowRunStatusIndicator = ({ status }: Props) => { + const style = getIconStyle(status); const classes = useStyles(style); const Icon = style.icon; return ( -
- -
+ +  {`${status}`} + ); }; diff --git a/plugins/github-actions/src/components/BuildStatusIndicator/index.ts b/plugins/github-actions/src/components/WorkflowRunStatusIndicator/index.ts similarity index 88% rename from plugins/github-actions/src/components/BuildStatusIndicator/index.ts rename to plugins/github-actions/src/components/WorkflowRunStatusIndicator/index.ts index 520de525ec..463970a30c 100644 --- a/plugins/github-actions/src/components/BuildStatusIndicator/index.ts +++ b/plugins/github-actions/src/components/WorkflowRunStatusIndicator/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { BuildStatusIndicator } from './BuildStatusIndicator'; +export { WorkflowRunStatusIndicator } from './WorkflowRunStatusIndicator'; diff --git a/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx b/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx similarity index 75% rename from plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx rename to plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx index 172b3611bf..a4cb576468 100644 --- a/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx @@ -22,24 +22,29 @@ import { Content, ContentHeader, SupportButton, + useApi, + configApiRef, } from '@backstage/core'; import { Grid } from '@material-ui/core'; import React from 'react'; -import { BuildListTable } from '../BuildListTable'; +import { WorkflowRunsTable } from '../WorkflowRunsTable'; -export const BuildListPage = () => { +export const WorkflowRunsPage = () => { + const configApi = useApi(configApiRef); + const repo = configApi.getString('github-actions.repo'); + const owner = configApi.getString('github-actions.owner'); return (
- + This plugin allows you to view and interact with your builds within the GitHub Actions environment. @@ -47,7 +52,7 @@ export const BuildListPage = () => { - + diff --git a/plugins/github-actions/src/components/BuildListPage/index.ts b/plugins/github-actions/src/components/WorkflowRunsPage/index.ts similarity index 91% rename from plugins/github-actions/src/components/BuildListPage/index.ts rename to plugins/github-actions/src/components/WorkflowRunsPage/index.ts index 2134913e51..ef511763f7 100644 --- a/plugins/github-actions/src/components/BuildListPage/index.ts +++ b/plugins/github-actions/src/components/WorkflowRunsPage/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { BuildListPage } from './BuildListPage'; +export { WorkflowRunsPage } from './WorkflowRunsPage'; diff --git a/plugins/github-actions/src/components/BuildListTable/BuildListTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx similarity index 78% rename from plugins/github-actions/src/components/BuildListTable/BuildListTable.tsx rename to plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index b5fd4636f5..a76f0002f9 100644 --- a/plugins/github-actions/src/components/BuildListTable/BuildListTable.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -19,20 +19,18 @@ import RetryIcon from '@material-ui/icons/Replay'; import GitHubIcon from '@material-ui/icons/GitHub'; import { Link as RouterLink } from 'react-router-dom'; import { - StatusError, - StatusWarning, StatusOK, StatusPending, StatusRunning, Table, TableColumn, } from '@backstage/core'; -import { useBuilds } from './useBuilds'; +import { useWorkflowRuns } from './useWorkflowRuns'; -export type Build = { +export type WorkflowRun = { id: string; - buildName: string; - buildUrl?: string; + message: string; + url?: string; source: { branchName: string; commit: { @@ -41,24 +39,20 @@ export type Build = { }; }; status: string; - onRestartClick: () => void; + onReRunClick: () => void; }; // retried, canceled, infrastructure_fail, timedout, not_run, running, failed, queued, scheduled, not_running, no_tests, fixed, success const getStatusComponent = (status: string | undefined = '') => { switch (status.toLowerCase()) { case 'queued': - case 'scheduled': return ; - case 'running': + case 'in_progress': return ; - case 'failed': - return ; - case 'success': + case 'completed': return ; - case 'canceled': default: - return ; + return ; } }; @@ -70,18 +64,21 @@ const generatedColumns: TableColumn[] = [ width: '150px', }, { - title: 'Build', - field: 'buildName', + title: 'Message', + field: 'message', highlight: true, - render: (row: Partial) => ( - - {row.buildName} + render: (row: Partial) => ( + + {row.message} ), }, { title: 'Source', - render: (row: Partial) => ( + render: (row: Partial) => ( <>

{row.source?.branchName}

{row.source?.commit.hash}

@@ -90,7 +87,7 @@ const generatedColumns: TableColumn[] = [ }, { title: 'Status', - render: (row: Partial) => ( + render: (row: Partial) => ( {getStatusComponent(row.status)} @@ -100,8 +97,8 @@ const generatedColumns: TableColumn[] = [ }, { title: 'Actions', - render: (row: Partial) => ( - + render: (row: Partial) => ( + ), @@ -112,7 +109,7 @@ const generatedColumns: TableColumn[] = [ type Props = { loading: boolean; retry: () => void; - builds?: Build[]; + runs?: WorkflowRun[]; projectName: string; page: number; onChangePage: (page: number) => void; @@ -121,13 +118,13 @@ type Props = { onChangePageSize: (pageSize: number) => void; }; -const BuildListTableView: FC = ({ +const WorkflowRunsTableView: FC = ({ projectName, loading, pageSize, page, retry, - builds, + runs, onChangePage, onChangePageSize, total, @@ -146,7 +143,7 @@ const BuildListTableView: FC = ({ onClick: () => retry(), }, ]} - data={builds ?? []} + data={runs ?? []} onChangePage={onChangePage} onChangeRowsPerPage={onChangePageSize} title={ @@ -161,19 +158,19 @@ const BuildListTableView: FC = ({ ); }; -export const BuildListTable = ({ +export const WorkflowRunsTable = ({ repo, owner, }: { repo: string; owner: string; }) => { - const [tableProps, { retry, setPage, setPageSize }] = useBuilds({ + const [tableProps, { retry, setPage, setPageSize }] = useWorkflowRuns({ repo, owner, }); return ( - {}; + const reRunWorkflow = async () => {}; - const { loading, value: builds, retry } = useAsyncRetry(async () => { + const { loading, value: runs, retry } = useAsyncRetry< + WorkflowRun[] + >(async () => { const token = await auth.getAccessToken(['repo', 'user']); return ( @@ -38,19 +48,25 @@ export function useBuilds({ repo, owner }: { repo: string; owner: string }) { // GitHub API pagination count starts from 1 .listWorkflowRuns({ token, owner, repo, pageSize, page: page + 1 }) .then( - (allBuilds: ActionsListWorkflowRunsForRepoResponseData): Build[] => { - setTotal(allBuilds.total_count); + ( + workflowRunsData: ActionsListWorkflowRunsForRepoResponseData, + ): WorkflowRun[] => { + setTotal(workflowRunsData.total_count); // Transformation here - return allBuilds.workflow_runs.map(run => ({ - buildName: run.head_commit.message, + return workflowRunsData.workflow_runs.map(run => ({ + message: run.head_commit.message, id: `${run.id}`, - onRestartClick: () => { - api.reRunWorkflow({ - token, - owner, - repo, - runId: run.id, - }); + onReRunClick: async () => { + try { + await api.reRunWorkflow({ + token, + owner, + repo, + runId: run.id, + }); + } catch (e) { + errorApi.post(e); + } }, source: { branchName: run.head_branch, @@ -63,7 +79,7 @@ export function useBuilds({ repo, owner }: { repo: string; owner: string }) { }, }, status: run.status, - buildUrl: run.url, + url: run.url, })); }, ) @@ -76,15 +92,15 @@ export function useBuilds({ repo, owner }: { repo: string; owner: string }) { page, pageSize, loading, - builds, + runs, projectName, total, }, { - builds, + runs, setPage, setPageSize, - restartBuild, + reRunWorkflow, retry, }, ] as const; diff --git a/plugins/github-actions/src/components/types.ts b/plugins/github-actions/src/components/types.ts new file mode 100644 index 0000000000..3174cd23c0 --- /dev/null +++ b/plugins/github-actions/src/components/types.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export type Step = { + name: string; + status: string; + conclusion: string; + number: number; // starts from 1 + started_at: string; + completed_at: string; +}; + +export type Job = { + html_url: string; + status: string; + conclusion: string; + started_at: string; + completed_at: string; + name: string; + steps: Step[]; +}; + +export type Jobs = { + total_count: number; + jobs: Job[]; +}; + +export enum BuildStatus { + 'success', + 'failure', + 'pending', + 'running', +} diff --git a/plugins/github-actions/src/plugin.ts b/plugins/github-actions/src/plugin.ts index b728296cbc..7faee4e624 100644 --- a/plugins/github-actions/src/plugin.ts +++ b/plugins/github-actions/src/plugin.ts @@ -15,8 +15,8 @@ */ import { createPlugin, createRouteRef } from '@backstage/core'; -import { BuildDetailsPage } from './components/BuildDetailsPage'; -import { BuildListPage } from './components/BuildListPage'; +import { WorkflowRunDetailsPage } from './components/WorkflowRunDetailsPage'; +import { WorkflowRunsPage } from './components/WorkflowRunsPage'; // TODO(freben): This is just a demo route for now export const rootRouteRef = createRouteRef({ @@ -24,14 +24,14 @@ export const rootRouteRef = createRouteRef({ title: 'GitHub Actions', }); export const buildRouteRef = createRouteRef({ - path: '/github-actions/build/:id', - title: 'GitHub Actions Build', + path: '/github-actions/workflow-run/:id', + title: 'GitHub Actions Workflow Run', }); export const plugin = createPlugin({ id: 'github-actions', register({ router }) { - router.addRoute(rootRouteRef, BuildListPage); - router.addRoute(buildRouteRef, BuildDetailsPage); + router.addRoute(rootRouteRef, WorkflowRunsPage); + router.addRoute(buildRouteRef, WorkflowRunDetailsPage); }, }); diff --git a/yarn.lock b/yarn.lock index 6c0ff99388..8b2af1f211 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13075,6 +13075,11 @@ moment@2.26.0, moment@^2.25.3, moment@^2.26.0: resolved "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a" integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw== +moment@^2.27.0: + version "2.27.0" + resolved "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" + integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== + monaco-editor@^0.20.0: version "0.20.0" resolved "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.20.0.tgz#5d5009343a550124426cb4d965a4d27a348b4dea" From b4e8a798b6284c9f5ced09fa0c516de789d9472e Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Thu, 23 Jul 2020 10:15:14 +0200 Subject: [PATCH 51/78] fix: PR comments --- app-config.yaml | 4 +- .../WorkflowRunDetailsPage.tsx | 211 +++++++++++------- .../WorkflowRunStatusIcon.tsx | 36 +++ .../index.ts | 2 +- .../WorkflowRunStatusIndicator.tsx | 79 ------- .../WorkflowRunsTable/WorkflowRunsTable.tsx | 33 +-- 6 files changed, 173 insertions(+), 192 deletions(-) create mode 100644 plugins/github-actions/src/components/WorkflowRunStatusIcon/WorkflowRunStatusIcon.tsx rename plugins/github-actions/src/components/{WorkflowRunStatusIndicator => WorkflowRunStatusIcon}/index.ts (88%) delete mode 100644 plugins/github-actions/src/components/WorkflowRunStatusIndicator/WorkflowRunStatusIndicator.tsx diff --git a/app-config.yaml b/app-config.yaml index 7dacdc56ac..9440c57854 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -21,8 +21,8 @@ organization: name: Spotify github-actions: - owner: CircleCITest3 - repo: try-ssr + owner: spotify + repo: backstage techdocs: storageUrl: https://techdocs-mock-sites.storage.googleapis.com diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx index 0bf72c77fd..6bda0f50b8 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx @@ -30,20 +30,30 @@ import { ExpansionPanelDetails, ExpansionPanel, ExpansionPanelSummary, - ListItem, - List, ListItemText, CircularProgress, + Grid, } from '@material-ui/core'; import moment from 'moment'; import React from 'react'; -import { Link, useApi, configApiRef } from '@backstage/core'; +import { + Link, + useApi, + configApiRef, + Page, + Header, + HeaderLabel, + Content, + ContentHeader, + SupportButton, + pageTheme, +} from '@backstage/core'; import { Job, Step, Jobs } from '../types'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { WorkflowRunStatusIndicator } from '../WorkflowRunStatusIndicator'; import { useWorkflowRunsDetails } from './useWorkflowRunsDetails'; import { useWorkflowRunJobs } from './useWorkflowRunJobs'; +import { WorkflowRunStatusIcon } from '../WorkflowRunStatusIcon/WorkflowRunStatusIcon'; const useStyles = makeStyles(theme => ({ root: { @@ -92,12 +102,18 @@ const getElapsedTime = (start: string, end: string) => { const StepView = ({ step }: { step: Step }) => { return ( - - - + + + + + + + {step.status} + + ); }; @@ -121,11 +137,13 @@ const JobListItem = ({ job, className }: { job: Job; className: string }) => { - - {job.steps.map((step: Step) => ( - - ))} - + + + {job.steps.map((step: Step) => ( + + ))} +
+
); @@ -154,75 +172,98 @@ export const WorkflowRunDetailsPage = () => { } return ( -
- - - - < - - - Workflow Run Details - - - - - - - Branch - - {details.value?.head_branch} - - - - Message - - {details.value?.head_commit.message} - - - - Commit ID - - {details.value?.head_commit.id} - - - - Status - - - - - - - - Author - - {`${details.value?.head_commit.author.name} (${details.value?.head_commit.author.email})`} - - - - Links - - - {details.value?.html_url && ( - - )} - - - - - Jobs - {jobs.loading ? ( - - ) : ( - - )} - - - -
-
-
+ +
+ + +
+ + + + This plugin allows you to view and interact with your builds within + the GitHub Actions environment. + + + + +
+ + + + < Back + + + + + + + + + Branch + + {details.value?.head_branch} + + + + Message + + + {details.value?.head_commit.message} + + + + + Commit ID + + {details.value?.head_commit.id} + + + + Status + + + {' '} + {details.value?.status.toUpperCase()} + + + + + Author + + {`${details.value?.head_commit.author.name} (${details.value?.head_commit.author.email})`} + + + + Links + + + {details.value?.html_url && ( + + )} + + + + + Jobs + {jobs.loading ? ( + + ) : ( + + )} + + + +
+
+
+
+
+
+
); }; diff --git a/plugins/github-actions/src/components/WorkflowRunStatusIcon/WorkflowRunStatusIcon.tsx b/plugins/github-actions/src/components/WorkflowRunStatusIcon/WorkflowRunStatusIcon.tsx new file mode 100644 index 0000000000..645224ad24 --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunStatusIcon/WorkflowRunStatusIcon.tsx @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { StatusPending, StatusRunning, StatusOK } from '@backstage/core'; +import React from 'react'; + +export const WorkflowRunStatusIcon = ({ + status, +}: { + status: string | undefined; +}) => { + if (status === undefined) return null; + switch (status.toLowerCase()) { + case 'queued': + return ; + case 'in_progress': + return ; + case 'completed': + return ; + default: + return ; + } +}; diff --git a/plugins/github-actions/src/components/WorkflowRunStatusIndicator/index.ts b/plugins/github-actions/src/components/WorkflowRunStatusIcon/index.ts similarity index 88% rename from plugins/github-actions/src/components/WorkflowRunStatusIndicator/index.ts rename to plugins/github-actions/src/components/WorkflowRunStatusIcon/index.ts index 463970a30c..05fff3f46d 100644 --- a/plugins/github-actions/src/components/WorkflowRunStatusIndicator/index.ts +++ b/plugins/github-actions/src/components/WorkflowRunStatusIcon/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { WorkflowRunStatusIndicator } from './WorkflowRunStatusIndicator'; +export { WorkflowRunStatusIcon } from './WorkflowRunStatusIcon'; diff --git a/plugins/github-actions/src/components/WorkflowRunStatusIndicator/WorkflowRunStatusIndicator.tsx b/plugins/github-actions/src/components/WorkflowRunStatusIndicator/WorkflowRunStatusIndicator.tsx deleted file mode 100644 index a22c60cdcb..0000000000 --- a/plugins/github-actions/src/components/WorkflowRunStatusIndicator/WorkflowRunStatusIndicator.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { IconComponent } from '@backstage/core'; -import { makeStyles, Theme, Typography } from '@material-ui/core'; -import ProgressIcon from '@material-ui/icons/Autorenew'; -import SuccessIcon from '@material-ui/icons/CheckCircle'; -import UnknownIcon from '@material-ui/icons/Help'; -import React from 'react'; - -type Props = { - status?: string; -}; - -type StatusStyle = { - icon: IconComponent; - color: string; -}; - -const styles: { [key: string]: StatusStyle } = { - unknown: { - icon: UnknownIcon, - color: '#f49b20', - }, - completed: { - icon: SuccessIcon, - color: '#1db855', - }, - queued: { - icon: UnknownIcon, - color: '#5BC0DE', - }, - in_progress: { - icon: ProgressIcon, - color: '#BEBEBE', - }, -}; - -const getIconStyle = (status?: string): StatusStyle => { - return styles[status ?? 'unknown'] ?? styles.unknown; -}; - -const useStyles = makeStyles({ - icon: style => ({ - color: style.color, - }), -}); - -export const WorkflowRunStatusIndicator = ({ status }: Props) => { - const style = getIconStyle(status); - const classes = useStyles(style); - const Icon = style.icon; - - return ( - -  {`${status}`} - - ); -}; diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index a76f0002f9..4c54d12121 100644 --- a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -18,14 +18,9 @@ import { Link, Typography, Box, IconButton } from '@material-ui/core'; import RetryIcon from '@material-ui/icons/Replay'; import GitHubIcon from '@material-ui/icons/GitHub'; import { Link as RouterLink } from 'react-router-dom'; -import { - StatusOK, - StatusPending, - StatusRunning, - Table, - TableColumn, -} from '@backstage/core'; +import { Table, TableColumn } from '@backstage/core'; import { useWorkflowRuns } from './useWorkflowRuns'; +import { WorkflowRunStatusIcon } from '../WorkflowRunStatusIcon'; export type WorkflowRun = { id: string; @@ -42,20 +37,6 @@ export type WorkflowRun = { onReRunClick: () => void; }; -// retried, canceled, infrastructure_fail, timedout, not_run, running, failed, queued, scheduled, not_running, no_tests, fixed, success -const getStatusComponent = (status: string | undefined = '') => { - switch (status.toLowerCase()) { - case 'queued': - return ; - case 'in_progress': - return ; - case 'completed': - return ; - default: - return ; - } -}; - const generatedColumns: TableColumn[] = [ { title: 'ID', @@ -79,19 +60,21 @@ const generatedColumns: TableColumn[] = [ { title: 'Source', render: (row: Partial) => ( - <> +

{row.source?.branchName}

{row.source?.commit.hash}

- +
), }, { title: 'Status', render: (row: Partial) => ( - {getStatusComponent(row.status)} + - {row.status} + + {row.status} + ), }, From 9bdb010432b9ad8a149d1f9bf4e819b91053aa4d Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Thu, 23 Jul 2020 19:35:00 +0200 Subject: [PATCH 52/78] Merge --- plugins/github-actions/package.json | 1 + .../WorkflowRunsTable/useWorkflowRuns.ts | 8 +- yarn.lock | 127 ++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index ce83f44e5f..eccc1449f9 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -25,6 +25,7 @@ "@backstage/core": "^0.1.1-alpha.16", "@backstage/core-api": "^0.1.1-alpha.16", "@backstage/theme": "^0.1.1-alpha.16", + "@backstage/plugin-catalog": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/useWorkflowRuns.ts b/plugins/github-actions/src/components/WorkflowRunsTable/useWorkflowRuns.ts index 62bffe122d..a2a3162bec 100644 --- a/plugins/github-actions/src/components/WorkflowRunsTable/useWorkflowRuns.ts +++ b/plugins/github-actions/src/components/WorkflowRunsTable/useWorkflowRuns.ts @@ -19,6 +19,7 @@ import { WorkflowRun } from './WorkflowRunsTable'; import { githubActionsApiRef } from '../../api/GithubActionsApi'; import { useApi, githubAuthApiRef, errorApiRef } from '@backstage/core'; import { ActionsListWorkflowRunsForRepoResponseData } from '@octokit/types'; +import { catalogApiRef } from '@backstage/plugin-catalog'; export function useWorkflowRuns({ repo, @@ -27,6 +28,7 @@ export function useWorkflowRuns({ repo: string; owner: string; }) { + const catalogApi = useApi(catalogApiRef); const api = useApi(githubActionsApiRef); const auth = useApi(githubAuthApiRef); @@ -42,7 +44,11 @@ export function useWorkflowRuns({ WorkflowRun[] >(async () => { const token = await auth.getAccessToken(['repo', 'user']); - + const entity = await catalogApi.getEntityByName({ + kind: 'Component', + name: 'backstage-site', + }); + console.log(entity); return ( api // GitHub API pagination count starts from 1 diff --git a/yarn.lock b/yarn.lock index 8b2af1f211..c662364db6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -939,6 +939,133 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@backstage/catalog-model@^0.1.1-alpha.15": + version "0.1.1-alpha.15" + resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.1.1-alpha.15.tgz#d1bb2eb53ff12af6868f49a24693fb2c0c209c78" + integrity sha512-nHfdeVpeyUpSyL4sjXRgCZ53ODP66o9Koc7oAXM2Gih07Z+thefIXDpY2bjWRcz+VLkVJJyVxRj/5VhrN2bukg== + dependencies: + "@backstage/config" "^0.1.1-alpha.13" + "@types/json-schema" "^7.0.5" + "@types/yup" "^0.28.2" + json-schema "^0.2.5" + lodash "^4.17.15" + uuid "^8.0.0" + yup "^0.29.1" + +"@backstage/core-api@^0.1.1-alpha.15": + version "0.1.1-alpha.15" + resolved "https://registry.npmjs.org/@backstage/core-api/-/core-api-0.1.1-alpha.15.tgz#c5d70339ee1b0646841c95f69665be9094b54331" + integrity sha512-U4MWV2MZS+0tEUod0HaugYEbsBB2VLG2t41UXm2kYYhLc3JfCr99DRR2KXJSyl2To6fc9//l7IMg4efpfhpWYw== + dependencies: + "@backstage/config" "^0.1.1-alpha.13" + "@backstage/theme" "^0.1.1-alpha.15" + "@material-ui/core" "^4.9.1" + "@material-ui/icons" "^4.9.1" + "@types/react" "^16.9" + prop-types "^15.7.2" + react "^16.12.0" + react-router-dom "6.0.0-beta.0" + react-use "^14.2.0" + zen-observable "^0.8.15" + +"@backstage/core@^0.1.1-alpha.15": + version "0.1.1-alpha.15" + resolved "https://registry.npmjs.org/@backstage/core/-/core-0.1.1-alpha.15.tgz#bb998f84643d6e6c88dfe6e8c3b1855cc35d7ed9" + integrity sha512-DaKwwj+mS5fa1Wq4R9+jbWMJdPj0JXv+Rzyy1iFKq9Ng9z1Rwum8h3R+q/DlPwSdSNlWaTdikZyw1C43Z+3FYw== + dependencies: + "@backstage/config" "^0.1.1-alpha.13" + "@backstage/core-api" "^0.1.1-alpha.15" + "@backstage/theme" "^0.1.1-alpha.15" + "@material-ui/core" "^4.9.1" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + "@types/react" "^16.9" + "@types/react-sparklines" "^1.7.0" + classnames "^2.2.6" + clsx "^1.1.0" + lodash "^4.17.15" + material-table "1.62.x" + prop-types "^15.7.2" + rc-progress "^3.0.0" + react "^16.12.0" + react-dom "^16.12.0" + react-helmet "6.1.0" + react-hook-form "^5.7.2" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-sparklines "^1.7.0" + react-syntax-highlighter "^12.2.1" + react-use "^14.2.0" + +"@backstage/plugin-catalog@^0.1.1-alpha.15": + version "0.1.1-alpha.15" + resolved "https://registry.npmjs.org/@backstage/plugin-catalog/-/plugin-catalog-0.1.1-alpha.15.tgz#438b2bf035ea2acf21cddda9c33363ffc6629b55" + integrity sha512-4wGs4AROaF+nq7DsQzpgdtifwjQnTfWRO541zSNh117qJe7yIHXzY+XVAzQ5ct421hYXn7ofxwKUsGI4hYgVRg== + dependencies: + "@backstage/catalog-model" "^0.1.1-alpha.15" + "@backstage/core" "^0.1.1-alpha.15" + "@backstage/plugin-scaffolder" "^0.1.1-alpha.15" + "@backstage/plugin-sentry" "^0.1.1-alpha.15" + "@backstage/theme" "^0.1.1-alpha.15" + "@material-ui/core" "^4.9.1" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + moment "^2.26.0" + react "^16.13.1" + react-dom "^16.13.1" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-use "^14.2.0" + swr "^0.2.2" + +"@backstage/plugin-scaffolder@^0.1.1-alpha.15": + version "0.1.1-alpha.15" + resolved "https://registry.npmjs.org/@backstage/plugin-scaffolder/-/plugin-scaffolder-0.1.1-alpha.15.tgz#935d21a5c815c530bd4fed89b9c1d4cb983a57bb" + integrity sha512-+HZ/aPlMcacuT7JYN2hYlZqWA8jYoAEoh7laBripm1nIxhxCLi4BuPX4X0W73a3cpCv+MYefvUq0g7yYDJvQJw== + dependencies: + "@backstage/catalog-model" "^0.1.1-alpha.15" + "@backstage/core" "^0.1.1-alpha.15" + "@backstage/plugin-catalog" "^0.1.1-alpha.15" + "@backstage/theme" "^0.1.1-alpha.15" + "@material-ui/core" "^4.9.1" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + "@rjsf/core" "^2.1.0" + "@rjsf/material-ui" "^2.1.0" + classnames "^2.2.6" + moment "^2.26.0" + react "^16.13.1" + react-dom "^16.13.1" + react-lazylog "^4.5.2" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-use "^14.2.0" + swr "^0.2.2" + +"@backstage/plugin-sentry@^0.1.1-alpha.15": + version "0.1.1-alpha.15" + resolved "https://registry.npmjs.org/@backstage/plugin-sentry/-/plugin-sentry-0.1.1-alpha.15.tgz#8a507f7143766582dc5bed7a9ee09ea256cbdd0d" + integrity sha512-wBOSqOvF6qedZ9gAYR3aQ8rqbgCUQz5WA5Mw7KZVa0FeWdjLIKYsDl0oI6EE3EQZV9iZef6kzY7W/8gjJV3fHQ== + dependencies: + "@backstage/core" "^0.1.1-alpha.15" + "@backstage/theme" "^0.1.1-alpha.15" + "@material-ui/core" "^4.9.1" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + "@types/react" "^16.9" + react "^16.13.1" + react-dom "^16.13.1" + react-sparklines "^1.7.0" + react-use "^14.2.0" + timeago.js "^4.0.2" + +"@backstage/theme@^0.1.1-alpha.15": + version "0.1.1-alpha.15" + resolved "https://registry.npmjs.org/@backstage/theme/-/theme-0.1.1-alpha.15.tgz#b30ec50ef0aba672f3b6f6ed3fb0209ec2990008" + integrity sha512-cyE3jj3Oynd8NHYSVm7H1Dhz573oe7V6vOAetdoqt7fX7I28onoSrUzv8x9eVM8uX4FiMck8PISPVKPGddCDZQ== + dependencies: + "@material-ui/core" "^4.9.1" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" From 25e41d227436cabec1f0786158a10de53b41f9c6 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Thu, 23 Jul 2020 21:05:55 +0200 Subject: [PATCH 53/78] feat: use annotation to get project name --- .../examples/shuffle-api-component.yaml | 2 + .../src/api/GithubActionsClient.ts | 2 +- plugins/github-actions/src/api/types.ts | 228 ++---------------- .../WorkflowRunDetailsPage.tsx | 28 +-- .../useWorkflowRunJobs.ts | 2 +- .../useWorkflowRunsDetails.ts | 4 +- .../WorkflowRunsPage/WorkflowRunsPage.tsx | 9 +- .../WorkflowRunsTable/WorkflowRunsTable.tsx | 28 +-- .../WorkflowRunsTable/useWorkflowRuns.ts | 31 +-- .../{types.ts => useProjectName.ts} | 40 +-- .../react-ssr-template/template.yaml | 17 +- yarn.lock | 127 ---------- 12 files changed, 95 insertions(+), 423 deletions(-) rename plugins/github-actions/src/components/{types.ts => useProjectName.ts} (54%) diff --git a/packages/catalog-model/examples/shuffle-api-component.yaml b/packages/catalog-model/examples/shuffle-api-component.yaml index 275c48d6ba..1726f7aec7 100644 --- a/packages/catalog-model/examples/shuffle-api-component.yaml +++ b/packages/catalog-model/examples/shuffle-api-component.yaml @@ -3,6 +3,8 @@ kind: Component metadata: name: shuffle-api description: Shuffle API + annotations: + backstage.io/github-actions-id: 'spotify/backstage' spec: type: service lifecycle: production diff --git a/plugins/github-actions/src/api/GithubActionsClient.ts b/plugins/github-actions/src/api/GithubActionsClient.ts index 6e6f1cb1e2..a96f5341fd 100644 --- a/plugins/github-actions/src/api/GithubActionsClient.ts +++ b/plugins/github-actions/src/api/GithubActionsClient.ts @@ -23,7 +23,7 @@ import { } from '@octokit/types'; export class GithubActionsClient implements GithubActionsApi { - reRunWorkflow({ + async reRunWorkflow({ token, owner, repo, diff --git a/plugins/github-actions/src/api/types.ts b/plugins/github-actions/src/api/types.ts index 8a7b2ca548..cd74a20cda 100644 --- a/plugins/github-actions/src/api/types.ts +++ b/plugins/github-actions/src/api/types.ts @@ -14,211 +14,33 @@ * limitations under the License. */ -export enum BuildStatus { - Null, - Success, - Failure, - Pending, - Running, -} - -export type Build = { - commitId: string; - message: string; - branch: string; - status: BuildStatus; - uri: string; -}; - -export type BuildDetails = { - build: Build; - author: string; - logUrl: string; - overviewUrl: string; -}; - -export interface Author { +export type Step = { name: string; - email: string; -} - -export interface Committer { - name: string; - email: string; -} - -export interface HeadCommit { - id: string; - tree_id: string; - message: string; - timestamp: Date; - author: Author; - committer: Committer; -} - -export interface Owner { - login: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: string; - site_admin: boolean; -} - -export interface Repository { - id: number; - node_id: string; - name: string; - full_name: string; - private: boolean; - owner: Owner; - html_url: string; - description?: any; - fork: boolean; - url: string; - forks_url: string; - keys_url: string; - collaborators_url: string; - teams_url: string; - hooks_url: string; - issue_events_url: string; - events_url: string; - assignees_url: string; - branches_url: string; - tags_url: string; - blobs_url: string; - git_tags_url: string; - git_refs_url: string; - trees_url: string; - statuses_url: string; - languages_url: string; - stargazers_url: string; - contributors_url: string; - subscribers_url: string; - subscription_url: string; - commits_url: string; - git_commits_url: string; - comments_url: string; - issue_comment_url: string; - contents_url: string; - compare_url: string; - merges_url: string; - archive_url: string; - downloads_url: string; - issues_url: string; - pulls_url: string; - milestones_url: string; - notifications_url: string; - labels_url: string; - releases_url: string; - deployments_url: string; -} - -export interface Owner2 { - login: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: string; - site_admin: boolean; -} - -export interface HeadRepository { - id: number; - node_id: string; - name: string; - full_name: string; - private: boolean; - owner: Owner2; - html_url: string; - description?: any; - fork: boolean; - url: string; - forks_url: string; - keys_url: string; - collaborators_url: string; - teams_url: string; - hooks_url: string; - issue_events_url: string; - events_url: string; - assignees_url: string; - branches_url: string; - tags_url: string; - blobs_url: string; - git_tags_url: string; - git_refs_url: string; - trees_url: string; - statuses_url: string; - languages_url: string; - stargazers_url: string; - contributors_url: string; - subscribers_url: string; - subscription_url: string; - commits_url: string; - git_commits_url: string; - comments_url: string; - issue_comment_url: string; - contents_url: string; - compare_url: string; - merges_url: string; - archive_url: string; - downloads_url: string; - issues_url: string; - pulls_url: string; - milestones_url: string; - notifications_url: string; - labels_url: string; - releases_url: string; - deployments_url: string; -} - -export interface WorkflowRun { - id: number; - node_id: string; - head_branch: string; - head_sha: string; - run_number: number; - event: string; status: string; conclusion: string; - workflow_id: number; - url: string; + number: number; // starts from 1 + started_at: string; + completed_at: string; +}; + +export type Job = { html_url: string; - pull_requests: any[]; - created_at: Date; - updated_at: Date; - jobs_url: string; - logs_url: string; - check_suite_url: string; - artifacts_url: string; - cancel_url: string; - rerun_url: string; - workflow_url: string; - head_commit: HeadCommit; - repository: Repository; - head_repository: HeadRepository; + status: string; + conclusion: string; + started_at: string; + completed_at: string; + name: string; + steps: Step[]; +}; + +export type Jobs = { + total_count: number; + jobs: Job[]; +}; + +export enum BuildStatus { + 'success', + 'failure', + 'pending', + 'running', } diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx index 6bda0f50b8..a8ddcfef12 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx @@ -33,14 +33,13 @@ import { ListItemText, CircularProgress, Grid, + Breadcrumbs, } from '@material-ui/core'; import moment from 'moment'; import React from 'react'; import { Link, - useApi, - configApiRef, Page, Header, HeaderLabel, @@ -49,11 +48,12 @@ import { SupportButton, pageTheme, } from '@backstage/core'; -import { Job, Step, Jobs } from '../types'; +import { Job, Step, Jobs } from '../../api/types'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { useWorkflowRunsDetails } from './useWorkflowRunsDetails'; import { useWorkflowRunJobs } from './useWorkflowRunJobs'; import { WorkflowRunStatusIcon } from '../WorkflowRunStatusIcon/WorkflowRunStatusIcon'; +import { useProjectName } from '../useProjectName'; const useStyles = makeStyles(theme => ({ root: { @@ -153,9 +153,12 @@ const JobListItem = ({ job, className }: { job: Job; className: string }) => { * A component for Jobs visualization. Jobs are a property of a Workflow Run. */ export const WorkflowRunDetailsPage = () => { - const configApi = useApi(configApiRef); - const repo = configApi.getString('github-actions.repo'); - const owner = configApi.getString('github-actions.owner'); + const [owner, repo] = ( + useProjectName({ + kind: 'Component', + name: 'backstage-site', + }) ?? '/' + ).split('/'); const details = useWorkflowRunsDetails(repo, owner); const jobs = useWorkflowRunJobs(details.value?.jobs_url); @@ -175,7 +178,7 @@ export const WorkflowRunDetailsPage = () => {
@@ -187,16 +190,13 @@ export const WorkflowRunDetailsPage = () => { the GitHub Actions environment. + + Workflow runs + Workflow run details +
- - - - < Back - - - diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunJobs.ts b/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunJobs.ts index c941ed6389..d4c09abf7d 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunJobs.ts +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunJobs.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { useAsync } from 'react-use'; -import { Jobs } from '../types'; +import { Jobs } from '../../api/types'; export const useWorkflowRunJobs = (jobsUrl?: string) => { const jobs = useAsync(async () => { diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunsDetails.ts b/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunsDetails.ts index 088c286d94..623f55eea6 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunsDetails.ts +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/useWorkflowRunsDetails.ts @@ -23,13 +23,13 @@ export const useWorkflowRunsDetails = (repo: string, owner: string) => { const auth = useApi(githubAuthApiRef); const { id } = useParams(); const details = useAsync(async () => { - const token = await auth.getAccessToken(['repo', 'user']); + const token = await auth.getAccessToken(['repo']); return api.getWorkflowRun({ token, owner, repo, id: parseInt(id, 10), }); - }, [id]); + }, [repo, owner, id]); return details; }; diff --git a/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx b/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx index a4cb576468..b75a70f326 100644 --- a/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx @@ -22,8 +22,6 @@ import { Content, ContentHeader, SupportButton, - useApi, - configApiRef, } from '@backstage/core'; import { Grid } from '@material-ui/core'; import React from 'react'; @@ -31,14 +29,11 @@ import React from 'react'; import { WorkflowRunsTable } from '../WorkflowRunsTable'; export const WorkflowRunsPage = () => { - const configApi = useApi(configApiRef); - const repo = configApi.getString('github-actions.repo'); - const owner = configApi.getString('github-actions.owner'); return (
@@ -52,7 +47,7 @@ export const WorkflowRunsPage = () => { - + diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index 4c54d12121..313a51e28a 100644 --- a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -14,13 +14,14 @@ * limitations under the License. */ import React, { FC } from 'react'; -import { Link, Typography, Box, IconButton } from '@material-ui/core'; +import { Link, Typography, Box, IconButton, Tooltip } from '@material-ui/core'; import RetryIcon from '@material-ui/icons/Replay'; import GitHubIcon from '@material-ui/icons/GitHub'; import { Link as RouterLink } from 'react-router-dom'; import { Table, TableColumn } from '@backstage/core'; import { useWorkflowRuns } from './useWorkflowRuns'; import { WorkflowRunStatusIcon } from '../WorkflowRunStatusIcon'; +import SyncIcon from '@material-ui/icons/Sync'; export type WorkflowRun = { id: string; @@ -81,9 +82,11 @@ const generatedColumns: TableColumn[] = [ { title: 'Actions', render: (row: Partial) => ( - - - + + + + + ), width: '10%', }, @@ -120,8 +123,8 @@ const WorkflowRunsTableView: FC = ({ page={page} actions={[ { - icon: () => , - tooltip: 'Refresh Data', + icon: () => , + tooltip: 'Reload workflow runs', isFreeAction: true, onClick: () => retry(), }, @@ -141,17 +144,8 @@ const WorkflowRunsTableView: FC = ({ ); }; -export const WorkflowRunsTable = ({ - repo, - owner, -}: { - repo: string; - owner: string; -}) => { - const [tableProps, { retry, setPage, setPageSize }] = useWorkflowRuns({ - repo, - owner, - }); +export const WorkflowRunsTable = () => { + const [tableProps, { retry, setPage, setPageSize }] = useWorkflowRuns(); return ( {}; - + const projectName = useProjectName({ + kind: 'Component', + name: 'backstage-site', + }); const { loading, value: runs, retry } = useAsyncRetry< WorkflowRun[] >(async () => { - const token = await auth.getAccessToken(['repo', 'user']); - const entity = await catalogApi.getEntityByName({ - kind: 'Component', - name: 'backstage-site', - }); - console.log(entity); + const token = await auth.getAccessToken(['repo']); + const [owner, repo] = (projectName ?? '/').split('/'); return ( api // GitHub API pagination count starts from 1 @@ -90,23 +81,21 @@ export function useWorkflowRuns({ }, ) ); - }, [page, pageSize]); + }, [page, pageSize, projectName]); - const projectName = `${owner}/${repo}`; return [ { page, pageSize, loading, runs, - projectName, + projectName: projectName ?? '', total, }, { runs, setPage, setPageSize, - reRunWorkflow, retry, }, ] as const; diff --git a/plugins/github-actions/src/components/types.ts b/plugins/github-actions/src/components/useProjectName.ts similarity index 54% rename from plugins/github-actions/src/components/types.ts rename to plugins/github-actions/src/components/useProjectName.ts index 3174cd23c0..d6fe9bd35d 100644 --- a/plugins/github-actions/src/components/types.ts +++ b/plugins/github-actions/src/components/useProjectName.ts @@ -13,33 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export type Step = { - name: string; - status: string; - conclusion: string; - number: number; // starts from 1 - started_at: string; - completed_at: string; -}; -export type Job = { - html_url: string; - status: string; - conclusion: string; - started_at: string; - completed_at: string; - name: string; - steps: Step[]; -}; +import { useAsync } from 'react-use'; +import { catalogApiRef, EntityCompoundName } from '@backstage/plugin-catalog'; +import { useApi } from '@backstage/core'; -export type Jobs = { - total_count: number; - jobs: Job[]; -}; +export const useProjectName = (name: EntityCompoundName) => { + const catalogApi = useApi(catalogApiRef); -export enum BuildStatus { - 'success', - 'failure', - 'pending', - 'running', -} + const { value } = useAsync(async () => { + const entity = await catalogApi.getEntityByName(name); + return ( + entity?.metadata.annotations?.['backstage.io/github-actions-id'] ?? '' + ); + }); + return value; +}; diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml b/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml index 71f75c7695..dc9fb22aa8 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml @@ -13,7 +13,7 @@ spec: type: website path: '.' schema: - required: + required: - component_id - description properties: @@ -22,7 +22,18 @@ spec: type: string description: Unique name of the component description: - title: Description + title: Description type: string description: Description of the component - +--- +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-site + description: backstage.io + annotations: + backstage.io/github-actions-id: 'spotify/backstage' +spec: + type: website + lifecycle: production + owner: guest diff --git a/yarn.lock b/yarn.lock index c662364db6..8b2af1f211 100644 --- a/yarn.lock +++ b/yarn.lock @@ -939,133 +939,6 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@backstage/catalog-model@^0.1.1-alpha.15": - version "0.1.1-alpha.15" - resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.1.1-alpha.15.tgz#d1bb2eb53ff12af6868f49a24693fb2c0c209c78" - integrity sha512-nHfdeVpeyUpSyL4sjXRgCZ53ODP66o9Koc7oAXM2Gih07Z+thefIXDpY2bjWRcz+VLkVJJyVxRj/5VhrN2bukg== - dependencies: - "@backstage/config" "^0.1.1-alpha.13" - "@types/json-schema" "^7.0.5" - "@types/yup" "^0.28.2" - json-schema "^0.2.5" - lodash "^4.17.15" - uuid "^8.0.0" - yup "^0.29.1" - -"@backstage/core-api@^0.1.1-alpha.15": - version "0.1.1-alpha.15" - resolved "https://registry.npmjs.org/@backstage/core-api/-/core-api-0.1.1-alpha.15.tgz#c5d70339ee1b0646841c95f69665be9094b54331" - integrity sha512-U4MWV2MZS+0tEUod0HaugYEbsBB2VLG2t41UXm2kYYhLc3JfCr99DRR2KXJSyl2To6fc9//l7IMg4efpfhpWYw== - dependencies: - "@backstage/config" "^0.1.1-alpha.13" - "@backstage/theme" "^0.1.1-alpha.15" - "@material-ui/core" "^4.9.1" - "@material-ui/icons" "^4.9.1" - "@types/react" "^16.9" - prop-types "^15.7.2" - react "^16.12.0" - react-router-dom "6.0.0-beta.0" - react-use "^14.2.0" - zen-observable "^0.8.15" - -"@backstage/core@^0.1.1-alpha.15": - version "0.1.1-alpha.15" - resolved "https://registry.npmjs.org/@backstage/core/-/core-0.1.1-alpha.15.tgz#bb998f84643d6e6c88dfe6e8c3b1855cc35d7ed9" - integrity sha512-DaKwwj+mS5fa1Wq4R9+jbWMJdPj0JXv+Rzyy1iFKq9Ng9z1Rwum8h3R+q/DlPwSdSNlWaTdikZyw1C43Z+3FYw== - dependencies: - "@backstage/config" "^0.1.1-alpha.13" - "@backstage/core-api" "^0.1.1-alpha.15" - "@backstage/theme" "^0.1.1-alpha.15" - "@material-ui/core" "^4.9.1" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/react" "^16.9" - "@types/react-sparklines" "^1.7.0" - classnames "^2.2.6" - clsx "^1.1.0" - lodash "^4.17.15" - material-table "1.62.x" - prop-types "^15.7.2" - rc-progress "^3.0.0" - react "^16.12.0" - react-dom "^16.12.0" - react-helmet "6.1.0" - react-hook-form "^5.7.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-sparklines "^1.7.0" - react-syntax-highlighter "^12.2.1" - react-use "^14.2.0" - -"@backstage/plugin-catalog@^0.1.1-alpha.15": - version "0.1.1-alpha.15" - resolved "https://registry.npmjs.org/@backstage/plugin-catalog/-/plugin-catalog-0.1.1-alpha.15.tgz#438b2bf035ea2acf21cddda9c33363ffc6629b55" - integrity sha512-4wGs4AROaF+nq7DsQzpgdtifwjQnTfWRO541zSNh117qJe7yIHXzY+XVAzQ5ct421hYXn7ofxwKUsGI4hYgVRg== - dependencies: - "@backstage/catalog-model" "^0.1.1-alpha.15" - "@backstage/core" "^0.1.1-alpha.15" - "@backstage/plugin-scaffolder" "^0.1.1-alpha.15" - "@backstage/plugin-sentry" "^0.1.1-alpha.15" - "@backstage/theme" "^0.1.1-alpha.15" - "@material-ui/core" "^4.9.1" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - moment "^2.26.0" - react "^16.13.1" - react-dom "^16.13.1" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^14.2.0" - swr "^0.2.2" - -"@backstage/plugin-scaffolder@^0.1.1-alpha.15": - version "0.1.1-alpha.15" - resolved "https://registry.npmjs.org/@backstage/plugin-scaffolder/-/plugin-scaffolder-0.1.1-alpha.15.tgz#935d21a5c815c530bd4fed89b9c1d4cb983a57bb" - integrity sha512-+HZ/aPlMcacuT7JYN2hYlZqWA8jYoAEoh7laBripm1nIxhxCLi4BuPX4X0W73a3cpCv+MYefvUq0g7yYDJvQJw== - dependencies: - "@backstage/catalog-model" "^0.1.1-alpha.15" - "@backstage/core" "^0.1.1-alpha.15" - "@backstage/plugin-catalog" "^0.1.1-alpha.15" - "@backstage/theme" "^0.1.1-alpha.15" - "@material-ui/core" "^4.9.1" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@rjsf/core" "^2.1.0" - "@rjsf/material-ui" "^2.1.0" - classnames "^2.2.6" - moment "^2.26.0" - react "^16.13.1" - react-dom "^16.13.1" - react-lazylog "^4.5.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^14.2.0" - swr "^0.2.2" - -"@backstage/plugin-sentry@^0.1.1-alpha.15": - version "0.1.1-alpha.15" - resolved "https://registry.npmjs.org/@backstage/plugin-sentry/-/plugin-sentry-0.1.1-alpha.15.tgz#8a507f7143766582dc5bed7a9ee09ea256cbdd0d" - integrity sha512-wBOSqOvF6qedZ9gAYR3aQ8rqbgCUQz5WA5Mw7KZVa0FeWdjLIKYsDl0oI6EE3EQZV9iZef6kzY7W/8gjJV3fHQ== - dependencies: - "@backstage/core" "^0.1.1-alpha.15" - "@backstage/theme" "^0.1.1-alpha.15" - "@material-ui/core" "^4.9.1" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/react" "^16.9" - react "^16.13.1" - react-dom "^16.13.1" - react-sparklines "^1.7.0" - react-use "^14.2.0" - timeago.js "^4.0.2" - -"@backstage/theme@^0.1.1-alpha.15": - version "0.1.1-alpha.15" - resolved "https://registry.npmjs.org/@backstage/theme/-/theme-0.1.1-alpha.15.tgz#b30ec50ef0aba672f3b6f6ed3fb0209ec2990008" - integrity sha512-cyE3jj3Oynd8NHYSVm7H1Dhz573oe7V6vOAetdoqt7fX7I28onoSrUzv8x9eVM8uX4FiMck8PISPVKPGddCDZQ== - dependencies: - "@material-ui/core" "^4.9.1" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" From 69a188e61324bab4de0ae3ef8a078ff9fb8008c6 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Fri, 24 Jul 2020 07:31:10 -0500 Subject: [PATCH 54/78] restore test: confirmed that yarn tsc fails --- plugins/welcome/src/components/WelcomePage/WelcomePage.tsx | 3 ++- plugins/welcome/src/components/WelcomePage/test.yaml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 plugins/welcome/src/components/WelcomePage/test.yaml diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx index 272297ed45..48292ae858 100644 --- a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx +++ b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx @@ -37,12 +37,13 @@ import { useApi, configApiRef, } from '@backstage/core'; +import uaml from './test.yaml' const WelcomePage = () => { const appTitle = useApi(configApiRef).getOptionalString('app.title') ?? 'Backstage'; const profile = { givenName: '' }; - +console.log(uaml); return (
Date: Fri, 24 Jul 2020 07:35:02 -0500 Subject: [PATCH 55/78] fix yarn tsc for yaml import --- packages/cli/asset-types/asset-types.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/cli/asset-types/asset-types.d.ts b/packages/cli/asset-types/asset-types.d.ts index 1763d256f5..28c1fde9e3 100644 --- a/packages/cli/asset-types/asset-types.d.ts +++ b/packages/cli/asset-types/asset-types.d.ts @@ -50,6 +50,11 @@ declare module '*.webp' { export default src; } +declare module '*.yaml' { + const src: string; + export default src; +} + declare module '*.icon.svg' { import { ComponentType } from 'react'; import { SvgIconProps } from '@material-ui/core'; From c3927c82d819900e50c00874f3e45267e64ea4c7 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Fri, 24 Jul 2020 07:35:19 -0500 Subject: [PATCH 56/78] Revert "restore test: confirmed that yarn tsc fails" This reverts commit 69a188e61324bab4de0ae3ef8a078ff9fb8008c6. --- plugins/welcome/src/components/WelcomePage/WelcomePage.tsx | 3 +-- plugins/welcome/src/components/WelcomePage/test.yaml | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 plugins/welcome/src/components/WelcomePage/test.yaml diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx index 48292ae858..272297ed45 100644 --- a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx +++ b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx @@ -37,13 +37,12 @@ import { useApi, configApiRef, } from '@backstage/core'; -import uaml from './test.yaml' const WelcomePage = () => { const appTitle = useApi(configApiRef).getOptionalString('app.title') ?? 'Backstage'; const profile = { givenName: '' }; -console.log(uaml); + return (
Date: Fri, 24 Jul 2020 15:56:48 +0200 Subject: [PATCH 57/78] feat(github-actions): add a separate data mock --- app-config.yaml | 10 +++------- plugins/github-actions/package.json | 3 ++- .../WorkflowRunDetailsPage.tsx | 2 +- .../components/WorkflowRunsTable/useWorkflowRuns.ts | 2 +- .../react-ssr-template/template.yaml | 12 ------------ 5 files changed, 7 insertions(+), 22 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 9440c57854..35cc7b6e1a 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -11,19 +11,15 @@ backend: credentials: true proxy: - "/circleci/api": - target: "https://circleci.com/api/v1.1" + '/circleci/api': + target: 'https://circleci.com/api/v1.1' changeOrigin: true pathRewrite: - "^/proxy/circleci/api/": "/" + '^/proxy/circleci/api/': '/' organization: name: Spotify -github-actions: - owner: spotify - repo: backstage - techdocs: storageUrl: https://techdocs-mock-sites.storage.googleapis.com diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index eccc1449f9..3f3e154b7b 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -18,7 +18,8 @@ "diff": "backstage-cli plugin:diff", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" }, "dependencies": { "@backstage/catalog-model": "^0.1.1-alpha.16", diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx index a8ddcfef12..308ae297d8 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx @@ -156,7 +156,7 @@ export const WorkflowRunDetailsPage = () => { const [owner, repo] = ( useProjectName({ kind: 'Component', - name: 'backstage-site', + name: 'backstage', }) ?? '/' ).split('/'); const details = useWorkflowRunsDetails(repo, owner); diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/useWorkflowRuns.ts b/plugins/github-actions/src/components/WorkflowRunsTable/useWorkflowRuns.ts index 753d08be54..4f72f88de0 100644 --- a/plugins/github-actions/src/components/WorkflowRunsTable/useWorkflowRuns.ts +++ b/plugins/github-actions/src/components/WorkflowRunsTable/useWorkflowRuns.ts @@ -33,7 +33,7 @@ export function useWorkflowRuns() { const projectName = useProjectName({ kind: 'Component', - name: 'backstage-site', + name: 'backstage', }); const { loading, value: runs, retry } = useAsyncRetry< WorkflowRun[] 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 dc9fb22aa8..ba0e877662 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml @@ -25,15 +25,3 @@ spec: title: Description type: string description: Description of the component ---- -apiVersion: backstage.io/v1alpha1 -kind: Component -metadata: - name: backstage-site - description: backstage.io - annotations: - backstage.io/github-actions-id: 'spotify/backstage' -spec: - type: website - lifecycle: production - owner: guest From c31b0e904998e5e3835f5b4d5c9372f1def4513a Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Fri, 24 Jul 2020 15:57:32 +0200 Subject: [PATCH 58/78] chore: remove obsolete library --- plugins/github-actions/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 3f3e154b7b..e20b719c31 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -35,7 +35,6 @@ "moment": "^2.27.0", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-lazylog": "^4.5.2", "react-router-dom": "6.0.0-beta.0", "react-use": "^14.2.0" }, From f6df232ed9545af74c17c71d44bb76c5fece8f28 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Fri, 24 Jul 2020 15:58:55 +0200 Subject: [PATCH 59/78] chore: remove mock data leftovers --- packages/catalog-model/examples/shuffle-api-component.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/catalog-model/examples/shuffle-api-component.yaml b/packages/catalog-model/examples/shuffle-api-component.yaml index 1726f7aec7..275c48d6ba 100644 --- a/packages/catalog-model/examples/shuffle-api-component.yaml +++ b/packages/catalog-model/examples/shuffle-api-component.yaml @@ -3,8 +3,6 @@ kind: Component metadata: name: shuffle-api description: Shuffle API - annotations: - backstage.io/github-actions-id: 'spotify/backstage' spec: type: service lifecycle: production From b94a1b3cfff1b4ac31f4019d2b9cb54065a0d9c6 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Fri, 24 Jul 2020 16:19:48 +0200 Subject: [PATCH 60/78] fix: add missing files --- plugins/github-actions/scripts/mock-data.sh | 8 ++++++++ plugins/github-actions/scripts/sample.yaml | 11 +++++++++++ 2 files changed, 19 insertions(+) create mode 100755 plugins/github-actions/scripts/mock-data.sh create mode 100644 plugins/github-actions/scripts/sample.yaml diff --git a/plugins/github-actions/scripts/mock-data.sh b/plugins/github-actions/scripts/mock-data.sh new file mode 100755 index 0000000000..2653a415dd --- /dev/null +++ b/plugins/github-actions/scripts/mock-data.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + + curl \ + --location \ + --request POST 'localhost:7000/catalog/locations' \ + --header 'Content-Type: application/json' \ + --data-raw "{\"type\": \"file\", \"target\": \"$(pwd)/scripts/sample.yaml\"}" + echo diff --git a/plugins/github-actions/scripts/sample.yaml b/plugins/github-actions/scripts/sample.yaml new file mode 100644 index 0000000000..3aa75200f5 --- /dev/null +++ b/plugins/github-actions/scripts/sample.yaml @@ -0,0 +1,11 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage + description: backstage.io + annotations: + backstage.io/github-actions-id: 'spotify/backstage' +spec: + type: website + lifecycle: production + owner: guest From b9db6d2017f7dc62d199f8b87fecad57c7427ee9 Mon Sep 17 00:00:00 2001 From: jaime-talkdesk <42963872+jaime-talkdesk@users.noreply.github.com> Date: Sat, 25 Jul 2020 12:46:44 +0100 Subject: [PATCH 61/78] Adds Talkdesk to Adopters.md (#1741) --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 63aae59401..3af423df9e 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -10,3 +10,4 @@ | [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 From 8777cb95ed1dc9f03c82f0eb0b44da6edbc044b4 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Sun, 26 Jul 2020 03:37:52 +0200 Subject: [PATCH 62/78] Update package.json --- plugins/graphql/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index be0681c18f..42f3dc9046 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.15", + "version": "0.1.1-alpha.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.15", + "@backstage/backend-common": "^0.1.1-alpha.16", "@types/express": "^4.17.6", "apollo-server": "^2.16.0", "apollo-server-express": "^2.16.0", @@ -33,7 +33,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.15", + "@backstage/cli": "^0.1.1-alpha.16", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.19.5", From 3158c2390b9f330598b903e7d25bf2297ffd6647 Mon Sep 17 00:00:00 2001 From: Paul Pacheco Date: Sun, 26 Jul 2020 01:56:30 -0500 Subject: [PATCH 63/78] images have extension jpg or jpeg, not jpe (#1742) --- packages/cli/config/jest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 23312bd308..da9b3ec3e6 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -38,7 +38,7 @@ async function getConfig() { transform: { '\\.esm\\.js$': require.resolve('jest-esm-transformer'), '\\.(js|jsx|ts|tsx)': require.resolve('ts-jest'), - '\\.(bmp|gif|jpe|png|frag|xml|svg)': require.resolve( + '\\.(bmp|gif|jpg|jpeg|png|frag|xml|svg)': require.resolve( './jestFileTransform.js', ), }, @@ -49,7 +49,7 @@ async function getConfig() { // Default behaviour is to not apply transforms for node_modules, but we still want // to apply the esm-transformer to .esm.js files, since that's what we use in backstage packages. transformIgnorePatterns: [ - '/node_modules/(?!.*\\.(?:esm\\.js|bmp|gif|jpe|png|frag|xml|svg)$)', + '/node_modules/(?!.*\\.(?:esm\\.js|bmp|gif|jpg|jpeg|png|frag|xml|svg)$)', ], }; From b83fcfcc2601e9a62cddfa788ef417d1e711ef9c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 26 Jul 2020 15:03:36 +0200 Subject: [PATCH 64/78] cli: remove dead code --- packages/cli/src/lib/bundler/config.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 0eaac9ad2d..34c0146a81 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -25,11 +25,6 @@ import { Config } from '@backstage/config'; import { BundlingPaths } from './paths'; import { transforms } from './transforms'; import { BundlingOptions, BackendBundlingOptions } from './types'; -// import checkRequiredFiles from 'react-dev-utils/checkRequiredFiles'; -// import ModuleNotFoundPlugin from 'react-dev-utils/ModuleNotFoundPlugin'; -// import errorOverlayMiddleware from 'react-dev-utils/errorOverlayMiddleware'; -// import evalSourceMapMiddleware from 'react-dev-utils/evalSourceMapMiddleware'; -// import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin'; export function resolveBaseUrl(config: Config): URL { const baseUrl = config.getString('app.baseUrl'); From 223759b0d01a365cd70c9c23db605c8d00fd7f70 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 26 Jul 2020 15:04:16 +0200 Subject: [PATCH 65/78] cli: put static assets in dist/static --- docker/run.sh | 2 +- packages/cli/src/lib/bundler/config.ts | 4 ++-- packages/cli/src/lib/bundler/optimization.ts | 2 +- packages/cli/src/lib/bundler/transforms.ts | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docker/run.sh b/docker/run.sh index 469fdd08cf..9cecd88f63 100755 --- a/docker/run.sh +++ b/docker/run.sh @@ -22,7 +22,7 @@ function inject_config() { >&2 echo "Runtime app config: $config" local main_js - if ! main_js="$(grep -l __APP_INJECTED_RUNTIME_CONFIG__ /usr/share/nginx/html/*.chunk.js)"; then + if ! main_js="$(grep -l __APP_INJECTED_RUNTIME_CONFIG__ /usr/share/nginx/html/static/*.js)"; then echo "Runtime config already written" return fi diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 34c0146a81..56150ad968 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -121,10 +121,10 @@ export function createConfig( output: { path: paths.targetDist, publicPath: validBaseUrl.pathname, - filename: isDev ? '[name].js' : '[name].[hash:8].js', + filename: isDev ? '[name].js' : 'static/[name].[hash:8].js', chunkFilename: isDev ? '[name].chunk.js' - : '[name].[chunkhash:8].chunk.js', + : 'static/[name].[chunkhash:8].chunk.js', }, plugins, }; diff --git a/packages/cli/src/lib/bundler/optimization.ts b/packages/cli/src/lib/bundler/optimization.ts index 03168b376d..acd6a937d9 100644 --- a/packages/cli/src/lib/bundler/optimization.ts +++ b/packages/cli/src/lib/bundler/optimization.ts @@ -46,7 +46,7 @@ export const optimization = ( }, filename: isDev ? 'module-[name].js' - : 'module-[name].[chunkhash:8].js', + : 'static/module-[name].[chunkhash:8].js', priority: 10, minSize: 100000, minChunks: 1, diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts index b498482f8a..d8186dac66 100644 --- a/packages/cli/src/lib/bundler/transforms.ts +++ b/packages/cli/src/lib/bundler/transforms.ts @@ -79,7 +79,7 @@ export const transforms = ( loader: require.resolve('url-loader'), options: { limit: 10000, - name: 'static/media/[name].[hash:8].[ext]', + name: 'static/[name].[hash:8].[ext]', }, }, { @@ -111,8 +111,8 @@ export const transforms = ( } else { plugins.push( new MiniCssExtractPlugin({ - filename: '[name].[contenthash:8].css', - chunkFilename: '[name].[id].[contenthash:8].css', + filename: 'static/[name].[contenthash:8].css', + chunkFilename: 'static/[name].[id].[contenthash:8].css', }), ); } From 13b62f2aff98ebd9e99b40c10af0f87ecc2f3585 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 26 Jul 2020 20:33:29 +0200 Subject: [PATCH 66/78] app: make bundling a regular build command --- .github/workflows/frontend.yml | 3 --- docs/getting-started/development-environment.md | 2 -- package.json | 3 +-- packages/app/package.json | 2 +- packages/cli/templates/default-app/package.json.hbs | 1 - .../cli/templates/default-app/packages/app/package.json.hbs | 2 +- 6 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index f154c586fa..0137e4b9dc 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -76,8 +76,5 @@ jobs: - name: verify plugin template run: yarn lerna -- run diff -- --check - - name: bundle example app - run: yarn bundle - - name: verify storybook run: yarn workspace storybook build-storybook diff --git a/docs/getting-started/development-environment.md b/docs/getting-started/development-environment.md index 02729e5bb4..e10ee4b24a 100644 --- a/docs/getting-started/development-environment.md +++ b/docs/getting-started/development-environment.md @@ -74,8 +74,6 @@ yarn test:all # test all packages yarn clean # Remove all output folders and @backstage/cli cache -yarn bundle # Build a production bundle of the example app - yarn diff # Make sure all plugins are up to date with the latest plugin template yarn create-plugin # Create a new plugin diff --git a/package.json b/package.json index 9d702c4755..500b688990 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ }, "scripts": { "start": "yarn workspace example-app start", - "bundle": "yarn workspace example-app bundle", "build": "lerna run build", "tsc": "tsc", "clean": "backstage-cli clean && lerna run clean", @@ -16,7 +15,7 @@ "lint": "lerna run lint --since origin/master --", "lint:all": "lerna run lint --", "lint:type-deps": "node scripts/check-type-dependencies.js", - "docker-build": "yarn bundle && docker build . -t spotify/backstage", + "docker-build": "yarn workspace example-app build && docker build . -t spotify/backstage", "docker-build:all": "yarn tsc && yarn build && yarn docker-build && yarn workspace example-backend build-image", "create-plugin": "backstage-cli create-plugin", "remove-plugin": "backstage-cli remove-plugin", diff --git a/packages/app/package.json b/packages/app/package.json index 4dc2f9c4c1..ea4c308380 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -51,7 +51,7 @@ }, "scripts": { "start": "backstage-cli app:serve", - "bundle": "backstage-cli app:build", + "build": "backstage-cli app:build", "clean": "backstage-cli clean", "test": "backstage-cli test", "test:e2e": "start-server-and-test start http://localhost:3000 cy:dev", diff --git a/packages/cli/templates/default-app/package.json.hbs b/packages/cli/templates/default-app/package.json.hbs index fb162e66d8..b59ca63fa3 100644 --- a/packages/cli/templates/default-app/package.json.hbs +++ b/packages/cli/templates/default-app/package.json.hbs @@ -7,7 +7,6 @@ }, "scripts": { "start": "yarn workspace app start", - "bundle": "yarn workspace app bundle", "build": "lerna run build", "tsc": "tsc", "clean": "backstage-cli clean && lerna run clean", diff --git a/packages/cli/templates/default-app/packages/app/package.json.hbs b/packages/cli/templates/default-app/packages/app/package.json.hbs index 26cce2c5e6..a34d25f216 100644 --- a/packages/cli/templates/default-app/packages/app/package.json.hbs +++ b/packages/cli/templates/default-app/packages/app/package.json.hbs @@ -32,7 +32,7 @@ }, "scripts": { "start": "backstage-cli app:serve", - "bundle": "backstage-cli app:build", + "build": "backstage-cli app:build", "test": "backstage-cli test", "lint": "backstage-cli lint", "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", From 88e3581ebcec6111189388d433f843ce6e48c06a Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Sat, 11 Jul 2020 15:38:16 -0400 Subject: [PATCH 67/78] feat: add backend status check handler middleware The statusCheckHandler is a simple express middleware that helps implement healthcheck/readiness endpoints. It can provided an optional status function that should return a promise that resolves in either true or false. The response will be either a 200 or 503 respectively. Any errors that occur will be forwarded to next. --- .../backend-common/src/middleware/index.ts | 1 + .../src/middleware/statusCheckHandler.test.ts | 55 +++++++++++++++++++ .../src/middleware/statusCheckHandler.ts | 51 +++++++++++++++++ .../src/service/lib/ServiceBuilderImpl.ts | 6 +- packages/backend-common/src/service/types.ts | 4 +- packages/backend/src/index.ts | 2 + 6 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 packages/backend-common/src/middleware/statusCheckHandler.test.ts create mode 100644 packages/backend-common/src/middleware/statusCheckHandler.ts diff --git a/packages/backend-common/src/middleware/index.ts b/packages/backend-common/src/middleware/index.ts index 083b36c3e9..76c52d8830 100644 --- a/packages/backend-common/src/middleware/index.ts +++ b/packages/backend-common/src/middleware/index.ts @@ -17,3 +17,4 @@ export * from './errorHandler'; export * from './notFoundHandler'; export * from './requestLoggingHandler'; +export * from './statusCheckHandler'; diff --git a/packages/backend-common/src/middleware/statusCheckHandler.test.ts b/packages/backend-common/src/middleware/statusCheckHandler.test.ts new file mode 100644 index 0000000000..7ed1a65b58 --- /dev/null +++ b/packages/backend-common/src/middleware/statusCheckHandler.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import express from 'express'; +import request from 'supertest'; +import { statusCheckHandler } from './statusCheckHandler'; + +describe('statusCheckHandler', () => { + it('gives status 200 when using default', async () => { + const app = express(); + app.use('/healthcheck', await statusCheckHandler()); + + const response = await request(app).get('/healthcheck'); + + expect(response.status).toBe(200); + expect(response.text).toBe(JSON.stringify({ status: 'ok' })); + }); + + it('gives status 200 when status function returns true', async () => { + const app = express(); + const status = { foo: 'bar' }; + const statusCheck = () => Promise.resolve(status); + app.use('/healthcheck', await statusCheckHandler({ statusCheck })); + + const response = await request(app).get('/healthcheck'); + + expect(response.status).toBe(200); + expect(response.text).toBe(JSON.stringify(status)); + }); + + it('gives status 500 when status check throws an error', async () => { + const app = express(); + const statusCheck = () => { + throw Error('error!'); + }; + app.use('/healthcheck', await statusCheckHandler({ statusCheck })); + + const response = await request(app).get('/healthcheck'); + + expect(response.status).toBe(500); + }); +}); diff --git a/packages/backend-common/src/middleware/statusCheckHandler.ts b/packages/backend-common/src/middleware/statusCheckHandler.ts new file mode 100644 index 0000000000..3f62f04f59 --- /dev/null +++ b/packages/backend-common/src/middleware/statusCheckHandler.ts @@ -0,0 +1,51 @@ +/* + * 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 { NextFunction, Request, Response, RequestHandler } from 'express'; + +export type StatusCheck = () => Promise; + +export interface StatusCheckHandlerOptions { + /** + * Optional status function which returns a message. + */ + statusCheck?: StatusCheck; +} + +/** + * Express middleware for status checks. + * + * This is commonly used to implement healthcheck and readiness routes. + * + * @param options An optional configuration object. + * @returns An Express error request handler + */ +export async function statusCheckHandler( + options: StatusCheckHandlerOptions = {}, +): Promise { + const statusCheck: StatusCheck = options.statusCheck + ? options.statusCheck + : () => Promise.resolve({ status: 'ok' }); + + return async (_request: Request, response: Response, next: NextFunction) => { + try { + const status = await statusCheck(); + response.status(200).header('').send(status); + } catch (err) { + next(err); + } + }; +} diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 4a51fb195c..eb07c03af5 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -17,7 +17,7 @@ import { ConfigReader } from '@backstage/config'; import compression from 'compression'; import cors from 'cors'; -import express, { Router } from 'express'; +import express, { Router, RequestHandler } from 'express'; import helmet from 'helmet'; import { Server } from 'http'; import stoppable from 'stoppable'; @@ -40,7 +40,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { private host: string | undefined; private logger: Logger | undefined; private corsOptions: cors.CorsOptions | undefined; - private routers: [string, Router][]; + private routers: [string, Router | RequestHandler][]; // Reference to the module where builder is created - needed for hot module // reloading private module: NodeModule; @@ -92,7 +92,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { return this; } - addRouter(root: string, router: Router): ServiceBuilder { + addRouter(root: string, router: Router | RequestHandler): ServiceBuilder { this.routers.push([root, router]); return this; } diff --git a/packages/backend-common/src/service/types.ts b/packages/backend-common/src/service/types.ts index b39df27527..070794969f 100644 --- a/packages/backend-common/src/service/types.ts +++ b/packages/backend-common/src/service/types.ts @@ -16,7 +16,7 @@ import { ConfigReader } from '@backstage/config'; import cors from 'cors'; -import { Router } from 'express'; +import { Router, RequestHandler } from 'express'; import { Server } from 'http'; import { Logger } from 'winston'; @@ -64,7 +64,7 @@ export type ServiceBuilder = { * @param root The root URL to bind to (e.g. "/api/function1") * @param router An express router */ - addRouter(root: string, router: Router): ServiceBuilder; + addRouter(root: string, router: Router | RequestHandler): ServiceBuilder; /** * Starts the server using the given settings. diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index d060791f98..9764f633b5 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -25,6 +25,7 @@ import { createServiceBuilder, getRootLogger, + statusCheckHandler, useHotMemoize, } from '@backstage/backend-common'; import { ConfigReader, AppConfig } from '@backstage/config'; @@ -73,6 +74,7 @@ async function main() { const service = createServiceBuilder(module) .loadConfig(configReader) + .addRouter('/healthcheck', await statusCheckHandler()) .addRouter('/catalog', await catalog(catalogEnv)) .addRouter('/rollbar', await rollbar(rollbarEnv)) .addRouter('/scaffolder', await scaffolder(scaffolderEnv)) From 2a482bd3ef2572bf9e748103aee6fe3de0c519c7 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Sun, 26 Jul 2020 16:11:23 -0400 Subject: [PATCH 68/78] add status check router --- packages/backend-common/package.json | 1 + .../service/createStatusCheckRouter.test.ts | 56 +++++++++++++++++++ .../src/service/createStatusCheckRouter.ts | 38 +++++++++++++ packages/backend-common/src/service/index.ts | 1 + .../src/service/lib/ServiceBuilderImpl.ts | 6 +- packages/backend/src/index.ts | 5 +- packages/backend/src/plugins/healthcheck.ts | 22 ++++++++ 7 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 packages/backend-common/src/service/createStatusCheckRouter.test.ts create mode 100644 packages/backend-common/src/service/createStatusCheckRouter.ts create mode 100644 packages/backend/src/plugins/healthcheck.ts diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 3c944846d1..18131236df 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -35,6 +35,7 @@ "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.17.1", + "express-promise-router": "^3.0.3", "helmet": "^3.22.0", "morgan": "^1.10.0", "stoppable": "^1.1.0", diff --git a/packages/backend-common/src/service/createStatusCheckRouter.test.ts b/packages/backend-common/src/service/createStatusCheckRouter.test.ts new file mode 100644 index 0000000000..6c15e7b5f9 --- /dev/null +++ b/packages/backend-common/src/service/createStatusCheckRouter.test.ts @@ -0,0 +1,56 @@ +/* + * 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 winston from 'winston'; +import request from 'supertest'; +import { createStatusCheckRouter } from './createStatusCheckRouter'; + +describe('createStatusCheckRouter', () => { + const logger = winston.createLogger(); + + it('gives status 200 when using default path', async () => { + const app = express(); + app.use('', await createStatusCheckRouter({ logger })); + + const response = await request(app).get('/healthcheck'); + + expect(response.status).toBe(200); + expect(response.text).toBe(JSON.stringify({ status: 'ok' })); + }); + + it('gives status 200 when using custom path', async () => { + const app = express(); + app.use('', await createStatusCheckRouter({ logger, path: '/ready' })); + + const response = await request(app).get('/ready'); + + expect(response.status).toBe(200); + expect(response.text).toBe(JSON.stringify({ status: 'ok' })); + }); + + it('gives status 500 when status check throws an error', async () => { + const app = express(); + const statusCheck = () => { + throw Error('error!'); + }; + app.use('', await createStatusCheckRouter({ logger, statusCheck })); + + const response = await request(app).get('/healthcheck'); + + expect(response.status).toBe(500); + }); +}); diff --git a/packages/backend-common/src/service/createStatusCheckRouter.ts b/packages/backend-common/src/service/createStatusCheckRouter.ts new file mode 100644 index 0000000000..c6014cbfc2 --- /dev/null +++ b/packages/backend-common/src/service/createStatusCheckRouter.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Logger } from 'winston'; +import Router from 'express-promise-router'; +import express from 'express'; +import { errorHandler, statusCheckHandler, StatusCheck } from '../middleware'; + +export interface StatusCheckRouterOptions { + logger: Logger; + path?: string; + statusCheck?: StatusCheck; +} + +export async function createStatusCheckRouter( + options: StatusCheckRouterOptions, +): Promise { + const router = Router(); + const { path = '/healthcheck', statusCheck } = options; + + router.use(path, await statusCheckHandler({ statusCheck })); + router.use(errorHandler()); + + return router; +} diff --git a/packages/backend-common/src/service/index.ts b/packages/backend-common/src/service/index.ts index 9bba9d6baf..58e310032d 100644 --- a/packages/backend-common/src/service/index.ts +++ b/packages/backend-common/src/service/index.ts @@ -15,4 +15,5 @@ */ export { createServiceBuilder } from './createServiceBuilder'; +export { createStatusCheckRouter } from './createStatusCheckRouter'; export type { ServiceBuilder } from './types'; diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index eb07c03af5..4a51fb195c 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -17,7 +17,7 @@ import { ConfigReader } from '@backstage/config'; import compression from 'compression'; import cors from 'cors'; -import express, { Router, RequestHandler } from 'express'; +import express, { Router } from 'express'; import helmet from 'helmet'; import { Server } from 'http'; import stoppable from 'stoppable'; @@ -40,7 +40,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { private host: string | undefined; private logger: Logger | undefined; private corsOptions: cors.CorsOptions | undefined; - private routers: [string, Router | RequestHandler][]; + private routers: [string, Router][]; // Reference to the module where builder is created - needed for hot module // reloading private module: NodeModule; @@ -92,7 +92,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { return this; } - addRouter(root: string, router: Router | RequestHandler): ServiceBuilder { + addRouter(root: string, router: Router): ServiceBuilder { this.routers.push([root, router]); return this; } diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 9764f633b5..a330353fa8 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -25,12 +25,12 @@ import { createServiceBuilder, getRootLogger, - statusCheckHandler, useHotMemoize, } from '@backstage/backend-common'; import { ConfigReader, AppConfig } from '@backstage/config'; import { loadConfig } from '@backstage/config-loader'; import knex from 'knex'; +import healthcheck from './plugins/healthcheck'; import auth from './plugins/auth'; import catalog from './plugins/catalog'; import identity from './plugins/identity'; @@ -63,6 +63,7 @@ async function main() { const configReader = ConfigReader.fromConfigs(configs); const createEnv = makeCreateEnv(configs); + const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck')); const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); const authEnv = useHotMemoize(module, () => createEnv('auth')); @@ -74,7 +75,7 @@ async function main() { const service = createServiceBuilder(module) .loadConfig(configReader) - .addRouter('/healthcheck', await statusCheckHandler()) + .addRouter('', await healthcheck(healthcheckEnv)) .addRouter('/catalog', await catalog(catalogEnv)) .addRouter('/rollbar', await rollbar(rollbarEnv)) .addRouter('/scaffolder', await scaffolder(scaffolderEnv)) diff --git a/packages/backend/src/plugins/healthcheck.ts b/packages/backend/src/plugins/healthcheck.ts new file mode 100644 index 0000000000..3dd9a67827 --- /dev/null +++ b/packages/backend/src/plugins/healthcheck.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createStatusCheckRouter } from '@backstage/backend-common'; +import { PluginEnvironment } from '../types'; + +export default async function createRouter({ logger }: PluginEnvironment) { + return await createStatusCheckRouter({ logger, path: '/healthcheck' }); +} From fa8e220f5c4ca360d4752b028c91d5efc86bd4a2 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Sun, 26 Jul 2020 20:51:54 -0400 Subject: [PATCH 69/78] feat: ensure errors are logged in errorHandler This adds winson logging for any 500 errors --- .../src/middleware/errorHandler.test.ts | 34 +++++++++++++++++++ .../src/middleware/errorHandler.ts | 19 +++++++++++ 2 files changed, 53 insertions(+) diff --git a/packages/backend-common/src/middleware/errorHandler.test.ts b/packages/backend-common/src/middleware/errorHandler.test.ts index e8a5a7f47a..a7a3d64bd1 100644 --- a/packages/backend-common/src/middleware/errorHandler.test.ts +++ b/packages/backend-common/src/middleware/errorHandler.test.ts @@ -96,4 +96,38 @@ describe('errorHandler', () => { expect((await r.get('/NotFoundError')).status).toBe(404); expect((await r.get('/ConflictError')).status).toBe(409); }); + + it('logs all 500 errors', async () => { + const app = express(); + + const mockLogger = { child: jest.fn(), error: jest.fn() }; + mockLogger.child.mockImplementation(() => mockLogger as any); + + const thrownError = new Error('some error'); + + app.use('/breaks', () => { + throw thrownError; + }); + app.use(errorHandler({ logger: mockLogger as any })); + + await request(app).get('/breaks'); + + expect(mockLogger.error).toHaveBeenCalledWith(thrownError); + }); + + it('does not log 400 errors', async () => { + const app = express(); + + const mockLogger = { child: jest.fn(), error: jest.fn() }; + mockLogger.child.mockImplementation(() => mockLogger as any); + + app.use('/NotFound', () => { + throw new errors.NotFoundError(); + }); + app.use(errorHandler({ logger: mockLogger as any })); + + await request(app).get('/NotFound'); + + expect(mockLogger.error).not.toHaveBeenCalled(); + }); }); diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index ad8f170dcd..798d173d38 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -15,7 +15,9 @@ */ import { ErrorRequestHandler, NextFunction, Request, Response } from 'express'; +import { Logger } from 'winston'; import * as errors from '../errors'; +import { getRootLogger } from '../logging'; export type ErrorHandlerOptions = { /** @@ -24,6 +26,13 @@ export type ErrorHandlerOptions = { * If not specified, by default shows stack traces only in development mode. */ showStackTraces?: boolean; + + /** + * Logger + * + * If not specified, by default shows stack traces only in development mode. + */ + logger?: Logger; }; /** @@ -39,12 +48,17 @@ export type ErrorHandlerOptions = { * * @returns An Express error request handler */ + export function errorHandler( options: ErrorHandlerOptions = {}, ): ErrorRequestHandler { const showStackTraces = options.showStackTraces ?? process.env.NODE_ENV === 'development'; + const logger = (options.logger || getRootLogger()).child({ + type: 'errorHandler', + }); + /* eslint-disable @typescript-eslint/no-unused-vars */ return ( error: Error, @@ -61,6 +75,11 @@ export function errorHandler( const status = getStatusCode(error); const message = showStackTraces ? error.stack : error.message; + + if (logger && status >= 500) { + logger.error(error); + } + response.status(status).send(message); }; } From 60d3ecc695e69977185fb48efe098a087eb99521 Mon Sep 17 00:00:00 2001 From: Raghunandan Date: Fri, 17 Jul 2020 13:24:30 +0200 Subject: [PATCH 70/78] Read auth providers in backend and frontend from app-config yaml --- app-config.yaml | 69 ++++++++++++++++ packages/app/src/App.tsx | 15 ++-- packages/backend/src/index.ts | 2 +- .../core/src/layout/Sidebar/UserSettings.tsx | 64 +++++++++------ .../core/src/layout/SignInPage/providers.tsx | 10 +-- .../auth-backend/src/providers/factories.ts | 36 ++++++--- .../src/providers/github/provider.ts | 66 +++++++-------- .../src/providers/gitlab/provider.ts | 73 ++++++++--------- .../src/providers/google/provider.ts | 64 +++++++-------- .../src/providers/oauth2/provider.ts | 81 +++++++++---------- .../src/providers/okta/provider.ts | 68 +++++++--------- .../src/providers/saml/provider.ts | 44 ++++------ plugins/auth-backend/src/providers/types.ts | 8 +- plugins/auth-backend/src/service/router.ts | 68 +--------------- 14 files changed, 333 insertions(+), 335 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 9a4a14b923..e76aba266e 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -25,3 +25,72 @@ techdocs: sentry: organization: spotify + +auth: + providers: + google: + development: + appOrigin: "http://localhost:3000/" + secure: false + clientId: + $secret: + env: AUTH_GOOGLE_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GOOGLE_CLIENT_SECRET + github: + development: + appOrigin: "http://localhost:3000/" + secure: false + clientId: + $secret: + env: AUTH_GITHUB_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GITHUB_CLIENT_SECRET + gitlab: + development: + appOrigin: "http://localhost:3000/" + secure: false + clientId: + $secret: + env: AUTH_GITLAB_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GITLAB_CLIENT_SECRET + audience: + $secret: + env: GITLAB_BASE_URL + # saml: + # development: + # entryPoint: "http://localhost:7001/" + # issuer: "passport-saml" + okta: + development: + appOrigin: "http://localhost:3000/" + secure: false + clientId: + $secret: + env: AUTH_OKTA_CLIENT_ID + clientSecret: + $secret: + env: AUTH_OKTA_CLIENT_SECRET + audience: + $secret: + env: AUTH_OKTA_AUDIENCE + oauth2: + development: + appOrigin: "http://localhost:3000/" + secure: false + clientId: + $secret: + env: AUTH_OAUTH2_CLIENT_ID + clientSecret: + $secret: + env: AUTH_OAUTH2_CLIENT_SECRET + authorizationURL: + $secret: + env: AUTH_OAUTH2_AUTH_URL + tokenURL: + $secret: + env: AUTH_OAUTH2_TOKEN_URL diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index c569308fc7..497b016c5c 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -19,6 +19,8 @@ import { AlertDisplay, OAuthRequestDialog, SignInPage, + useApi, + configApiRef, } from '@backstage/core'; import React, { FC } from 'react'; import Root from './components/Root'; @@ -30,12 +32,13 @@ const app = createApp({ apis, plugins: Object.values(plugins), components: { - SignInPage: props => ( - - ), + SignInPage: props => { + const configApi = useApi(configApiRef); + const providersConfig = configApi.getConfig('auth.providers'); + const providers = providersConfig.keys(); + + return ; + }, }, }); diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index d060791f98..a8b862c423 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -58,7 +58,7 @@ function makeCreateEnv(loadedConfigs: AppConfig[]) { } async function main() { - const configs = await loadConfig(); + const configs = await loadConfig({ shouldReadSecrets: true }); const configReader = ConfigReader.fromConfigs(configs); const createEnv = makeCreateEnv(configs); diff --git a/packages/core/src/layout/Sidebar/UserSettings.tsx b/packages/core/src/layout/Sidebar/UserSettings.tsx index 5f66fc6558..98362a6b9a 100644 --- a/packages/core/src/layout/Sidebar/UserSettings.tsx +++ b/packages/core/src/layout/Sidebar/UserSettings.tsx @@ -22,6 +22,7 @@ import { oauth2ApiRef, oktaAuthApiRef, useApi, + configApiRef, } from '@backstage/core-api'; import Collapse from '@material-ui/core/Collapse'; import SignOutIcon from '@material-ui/icons/MeetingRoom'; @@ -39,6 +40,9 @@ export function SidebarUserSettings() { const { isOpen: sidebarOpen } = useContext(SidebarContext); const [open, setOpen] = React.useState(false); const identityApi = useApi(identityApiRef); + const configApi = useApi(configApiRef); + const providersConfig = configApi.getConfig('auth.providers'); + const providers = providersConfig.keys(); // Close the provider list when sidebar collapse useEffect(() => { @@ -49,31 +53,41 @@ export function SidebarUserSettings() { <> - - - - - + {providers.includes('google') && ( + + )} + {providers.includes('github') && ( + + )} + {providers.includes('gitlab') && ( + + )} + {providers.includes('okta') && ( + + )} + {providers.includes('oauth2') && ( + + )} { @@ -46,17 +51,28 @@ export const createAuthProviderRouter = ( throw Error(`No auth provider available for '${providerId}'`); } - const provider = factory(globalConfig, providerConfig, logger, issuer); - const router = Router(); - router.get('/start', provider.start.bind(provider)); - router.get('/handler/frame', provider.frameHandler.bind(provider)); - router.post('/handler/frame', provider.frameHandler.bind(provider)); - if (provider.logout) { - router.post('/logout', provider.logout.bind(provider)); + const envs = providerConfig.keys(); + const envProviders: EnvironmentHandlers = {}; + + for (const env of envs) { + const envConfig = providerConfig.getConfig(env); + const provider = factory(globalConfig, env, envConfig, logger, issuer); + if (provider) { + envProviders[env] = provider; + } } - if (provider.refresh) { - router.get('/refresh', provider.refresh.bind(provider)); + + const handler = new EnvironmentHandler(providerId, envProviders); + + router.get('/start', handler.start.bind(handler)); + router.get('/handler/frame', handler.frameHandler.bind(handler)); + router.post('/handler/frame', handler.frameHandler.bind(handler)); + if (handler.logout) { + router.post('/logout', handler.logout.bind(handler)); + } + if (handler.refresh) { + router.get('/refresh', handler.refresh.bind(handler)); } return router; diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index bd7d4aa712..a67e78febd 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -25,20 +25,15 @@ import { OAuthProviderHandlers, AuthProviderConfig, RedirectInfo, - EnvironmentProviderConfig, OAuthProviderOptions, - OAuthProviderConfig, OAuthResponse, PassportDoneCallback, } from '../types'; import { OAuthProvider } from '../../lib/OAuthProvider'; -import { - EnvironmentHandlers, - EnvironmentHandler, -} from '../../lib/EnvironmentHandler'; import { Logger } from 'winston'; import { TokenIssuer } from '../../identity'; import passport from 'passport'; +import { Config } from '@backstage/config'; export class GithubAuthProvider implements OAuthProviderHandlers { private readonly _strategy: GithubStrategy; @@ -131,44 +126,43 @@ export class GithubAuthProvider implements OAuthProviderHandlers { export function createGithubProvider( { baseUrl }: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + env: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { const providerId = 'github'; - const envProviders: EnvironmentHandlers = {}; + const secure = envConfig.getBoolean('secure'); + const appOrigin = envConfig.getString('appOrigin'); + const clientID = envConfig.getOptionalString('clientId'); + const clientSecret = envConfig.getOptionalString('clientSecret'); + const callbackURL = `${baseUrl}/${providerId}/handler/frame?env=${env}`; - for (const [env, envConfig] of Object.entries(providerConfig)) { - const config = (envConfig as unknown) as OAuthProviderConfig; - const { secure, appOrigin } = config; - const opts = { - clientID: config.clientId, - clientSecret: config.clientSecret, - callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, - }; + const opts = { + clientID, + clientSecret, + callbackURL, + }; - if (!opts.clientID || !opts.clientSecret) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Failed to initialize Github auth provider, set AUTH_GITHUB_CLIENT_ID and AUTH_GITHUB_CLIENT_SECRET env vars', - ); - } - - logger.warn( - 'Github auth provider disabled, set AUTH_GITHUB_CLIENT_ID and AUTH_GITHUB_CLIENT_SECRET env vars to enable', + if (!opts.clientID || !opts.clientSecret) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize Github auth provider, set AUTH_GITHUB_CLIENT_ID and AUTH_GITHUB_CLIENT_SECRET env vars', ); - continue; } - envProviders[env] = new OAuthProvider(new GithubAuthProvider(opts), { - disableRefresh: true, - persistScopes: true, - providerId, - secure, - baseUrl, - appOrigin, - tokenIssuer, - }); + logger.warn( + 'Github auth provider disabled, set AUTH_GITHUB_CLIENT_ID and AUTH_GITHUB_CLIENT_SECRET env vars to enable', + ); + return undefined; } - return new EnvironmentHandler(providerId, envProviders); + return new OAuthProvider(new GithubAuthProvider(opts), { + disableRefresh: true, + persistScopes: true, + providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); } diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index af1d57fcce..78b1a55211 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -25,20 +25,15 @@ import { OAuthProviderHandlers, AuthProviderConfig, RedirectInfo, - EnvironmentProviderConfig, OAuthProviderOptions, - OAuthProviderConfig, OAuthResponse, PassportDoneCallback, } from '../types'; import { OAuthProvider } from '../../lib/OAuthProvider'; -import { - EnvironmentHandlers, - EnvironmentHandler, -} from '../../lib/EnvironmentHandler'; import { Logger } from 'winston'; import { TokenIssuer } from '../../identity'; import passport from 'passport'; +import { Config } from '@backstage/config'; export class GitlabAuthProvider implements OAuthProviderHandlers { private readonly _strategy: GitlabStrategy; @@ -138,49 +133,45 @@ export class GitlabAuthProvider implements OAuthProviderHandlers { export function createGitlabProvider( { baseUrl }: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + env: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { const providerId = 'gitlab'; - const envProviders: EnvironmentHandlers = {}; + const secure = envConfig.getBoolean('secure'); + const appOrigin = envConfig.getString('appOrigin'); + const clientID = envConfig.getOptionalString('clientId'); + const clientSecret = envConfig.getOptionalString('clientSecret'); + const audience = envConfig.getOptionalString('audience'); + const baseURL = audience || 'https://gitlab.com'; + const callbackURL = `${baseUrl}/${providerId}/handler/frame?env=${env}`; - for (const [env, envConfig] of Object.entries(providerConfig)) { - const { - secure, - appOrigin, - clientId, - clientSecret, - audience, - } = (envConfig as unknown) as OAuthProviderConfig; - const opts = { - clientID: clientId, - clientSecret: clientSecret, - callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, - baseURL: audience, - }; + const opts = { + clientID, + clientSecret, + callbackURL, + baseURL, + }; - if (!opts.clientID || !opts.clientSecret) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Failed to initialize Gitlab auth provider, set AUTH_GITLAB_CLIENT_ID and AUTH_GITLAB_CLIENT_SECRET env vars', - ); - } - - logger.warn( - 'Gitlab auth provider disabled, set AUTH_GITLAB_CLIENT_ID and AUTH_GITLAB_CLIENT_SECRET env vars to enable', + if (!opts.clientID || !opts.clientSecret) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize Gitlab auth provider, set AUTH_GITLAB_CLIENT_ID and AUTH_GITLAB_CLIENT_SECRET env vars', ); - continue; } - envProviders[env] = new OAuthProvider(new GitlabAuthProvider(opts), { - disableRefresh: true, - providerId, - secure, - baseUrl, - appOrigin, - tokenIssuer, - }); + logger.warn( + 'Gitlab auth provider disabled, set AUTH_GITLAB_CLIENT_ID and AUTH_GITLAB_CLIENT_SECRET env vars to enable', + ); + return undefined; } - return new EnvironmentHandler(providerId, envProviders); + return new OAuthProvider(new GitlabAuthProvider(opts), { + disableRefresh: true, + providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); } diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 3023c09886..5749d1465f 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -27,20 +27,15 @@ import { OAuthProviderHandlers, RedirectInfo, AuthProviderConfig, - EnvironmentProviderConfig, OAuthProviderOptions, - OAuthProviderConfig, OAuthResponse, PassportDoneCallback, } from '../types'; import { OAuthProvider } from '../../lib/OAuthProvider'; import passport from 'passport'; -import { - EnvironmentHandler, - EnvironmentHandlers, -} from '../../lib/EnvironmentHandler'; import { Logger } from 'winston'; import { TokenIssuer } from '../../identity'; +import { Config } from '@backstage/config'; type PrivateInfo = { refreshToken: string; @@ -150,43 +145,42 @@ export class GoogleAuthProvider implements OAuthProviderHandlers { export function createGoogleProvider( { baseUrl }: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + env: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { const providerId = 'google'; - const envProviders: EnvironmentHandlers = {}; + const secure = envConfig.getBoolean('secure'); + const appOrigin = envConfig.getString('appOrigin'); + const clientID = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); + const callbackURL = `${baseUrl}/${providerId}/handler/frame?env=${env}`; - for (const [env, envConfig] of Object.entries(providerConfig)) { - const config = (envConfig as unknown) as OAuthProviderConfig; - const { secure, appOrigin } = config; - const opts = { - clientID: config.clientId, - clientSecret: config.clientSecret, - callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, - }; + const opts = { + clientID, + clientSecret, + callbackURL, + }; - if (!opts.clientID || !opts.clientSecret) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Failed to initialize Google auth provider, set AUTH_GOOGLE_CLIENT_ID and AUTH_GOOGLE_CLIENT_SECRET env vars', - ); - } - - logger.warn( - 'Google auth provider disabled, set AUTH_GOOGLE_CLIENT_ID and AUTH_GOOGLE_CLIENT_SECRET env vars to enable', + if (!opts.clientID || !opts.clientSecret) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize Google auth provider, set AUTH_GOOGLE_CLIENT_ID and AUTH_GOOGLE_CLIENT_SECRET env vars', ); - continue; } - envProviders[env] = new OAuthProvider(new GoogleAuthProvider(opts), { - disableRefresh: false, - providerId, - secure, - baseUrl, - appOrigin, - tokenIssuer, - }); + logger.warn( + 'Google auth provider disabled, set AUTH_GOOGLE_CLIENT_ID and AUTH_GOOGLE_CLIENT_SECRET env vars to enable', + ); + return undefined; } - return new EnvironmentHandler(providerId, envProviders); + return new OAuthProvider(new GoogleAuthProvider(opts), { + disableRefresh: false, + providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); } diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index 60b6bc605b..e4258605ac 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -19,10 +19,6 @@ import passport from 'passport'; import { Strategy as OAuth2Strategy } from 'passport-oauth2'; import { Logger } from 'winston'; import { TokenIssuer } from '../../identity'; -import { - EnvironmentHandler, - EnvironmentHandlers, -} from '../../lib/EnvironmentHandler'; import { OAuthProvider } from '../../lib/OAuthProvider'; import { executeFetchUserProfileStrategy, @@ -33,14 +29,13 @@ import { } from '../../lib/PassportStrategyHelper'; import { AuthProviderConfig, - EnvironmentProviderConfig, - GenericOAuth2ProviderConfig, GenericOAuth2ProviderOptions, OAuthProviderHandlers, OAuthResponse, PassportDoneCallback, RedirectInfo, } from '../types'; +import { Config } from '@backstage/config'; type PrivateInfo = { refreshToken: string; @@ -148,51 +143,51 @@ export class OAuth2AuthProvider implements OAuthProviderHandlers { export function createOAuth2Provider( { baseUrl }: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + env: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { const providerId = 'oauth2'; - const envProviders: EnvironmentHandlers = {}; + const secure = envConfig.getBoolean('secure'); + const appOrigin = envConfig.getString('appOrigin'); + const clientID = envConfig.getOptionalString('clientId'); + const clientSecret = envConfig.getOptionalString('clientSecret'); + const callbackURL = `${baseUrl}/${providerId}/handler/frame?env=${env}`; + const authorizationURL = envConfig.getOptionalString('authorizationURL'); + const tokenURL = envConfig.getOptionalString('tokenURL'); - for (const [env, envConfig] of Object.entries(providerConfig)) { - const config = (envConfig as unknown) as GenericOAuth2ProviderConfig; - const { secure, appOrigin } = config; - const opts = { - clientID: config.clientId, - clientSecret: config.clientSecret, - callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, - authorizationURL: config.authorizationURL, - tokenURL: config.tokenURL, - }; + const opts = { + clientID, + clientSecret, + callbackURL, + authorizationURL, + tokenURL, + }; - if ( - !opts.clientID || - !opts.clientSecret || - !opts.authorizationURL || - !opts.tokenURL - ) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Failed to initialize OAuth2 auth provider, set AUTH_OAUTH2_CLIENT_ID, AUTH_OAUTH2_CLIENT_SECRET, AUTH_OAUTH2_AUTH_URL, and AUTH_OAUTH2_TOKEN_URL env vars', - ); - } - - logger.warn( - 'OAuth2 auth provider disabled, set AUTH_OAUTH2_CLIENT_ID, AUTH_OAUTH2_CLIENT_SECRET, AUTH_OAUTH2_AUTH_URL, and AUTH_OAUTH2_TOKEN_URL env vars to enable', + if ( + !opts.clientID || + !opts.clientSecret || + !opts.authorizationURL || + !opts.tokenURL + ) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize OAuth2 auth provider, set AUTH_OAUTH2_CLIENT_ID, AUTH_OAUTH2_CLIENT_SECRET, AUTH_OAUTH2_AUTH_URL, and AUTH_OAUTH2_TOKEN_URL env vars', ); - continue; } - envProviders[env] = new OAuthProvider(new OAuth2AuthProvider(opts), { - disableRefresh: false, - providerId, - secure, - baseUrl, - appOrigin, - tokenIssuer, - }); + logger.warn( + 'OAuth2 auth provider disabled, set AUTH_OAUTH2_CLIENT_ID, AUTH_OAUTH2_CLIENT_SECRET, AUTH_OAUTH2_AUTH_URL, and AUTH_OAUTH2_TOKEN_URL env vars to enable', + ); + return undefined; } - - return new EnvironmentHandler(providerId, envProviders); + return new OAuthProvider(new OAuth2AuthProvider(opts), { + disableRefresh: false, + providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); } diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 01d7684e97..5fcc7cb778 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -28,19 +28,14 @@ import { OAuthProviderHandlers, RedirectInfo, AuthProviderConfig, - EnvironmentProviderConfig, OAuthProviderOptions, - OAuthProviderConfig, OAuthResponse, PassportDoneCallback, } from '../types'; -import { - EnvironmentHandler, - EnvironmentHandlers, -} from '../../lib/EnvironmentHandler'; import { Logger } from 'winston'; import { StateStore } from 'passport-oauth2'; import { TokenIssuer } from '../../identity'; +import { Config } from '@backstage/config'; type PrivateInfo = { refreshToken: string; @@ -170,45 +165,44 @@ export class OktaAuthProvider implements OAuthProviderHandlers { export function createOktaProvider( { baseUrl }: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + env: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { const providerId = 'okta'; - const envProviders: EnvironmentHandlers = {}; + const secure = envConfig.getBoolean('secure'); + const appOrigin = envConfig.getString('appOrigin'); + const clientID = envConfig.getOptionalString('clientId'); + const clientSecret = envConfig.getOptionalString('clientSecret'); + const audience = envConfig.getOptionalString('audience'); + const callbackURL = `${baseUrl}/${providerId}/handler/frame?env=${env}`; - for (const [env, envConfig] of Object.entries(providerConfig)) { - const config = (envConfig as unknown) as OAuthProviderConfig; - const { secure, appOrigin } = config; - const opts = { - audience: config.audience, - clientID: config.clientId, - clientSecret: config.clientSecret, - callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, - }; + const opts = { + audience, + clientID, + clientSecret, + callbackURL, + }; - if (!opts.clientID || !opts.clientSecret || !opts.audience) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Failed to initialize Okta auth provider, set AUTH_OKTA_CLIENT_ID, AUTH_OKTA_CLIENT_SECRET, and AUTH_OKTA_AUDIENCE env vars', - ); - } - - logger.warn( - 'Okta auth provider disabled, set AUTH_OKTA_CLIENT_ID, AUTH_OKTA_CLIENT_SECRET, and AUTH_OKTA_AUDIENCE env vars to enable', + if (!opts.clientID || !opts.clientSecret || !opts.audience) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize Okta auth provider, set AUTH_OKTA_CLIENT_ID, AUTH_OKTA_CLIENT_SECRET, and AUTH_OKTA_AUDIENCE env vars', ); - continue; } - envProviders[env] = new OAuthProvider(new OktaAuthProvider(opts), { - disableRefresh: false, - providerId, - secure, - baseUrl, - appOrigin, - tokenIssuer, - }); + logger.warn( + 'Okta auth provider disabled, set AUTH_OKTA_CLIENT_ID, AUTH_OKTA_CLIENT_SECRET, and AUTH_OKTA_AUDIENCE env vars to enable', + ); + return undefined; } - - return new EnvironmentHandler(providerId, envProviders); + return new OAuthProvider(new OktaAuthProvider(opts), { + disableRefresh: false, + providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); } diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index 40c80de4e4..3ddfba4bb0 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -27,18 +27,13 @@ import { import { AuthProviderConfig, AuthProviderRouteHandlers, - EnvironmentProviderConfig, - SAMLProviderConfig, PassportDoneCallback, ProfileInfo, } from '../types'; import { postMessageResponse } from '../../lib/OAuthProvider'; -import { - EnvironmentHandlers, - EnvironmentHandler, -} from '../../lib/EnvironmentHandler'; import { Logger } from 'winston'; import { TokenIssuer } from '../../identity'; +import { Config } from '@backstage/config'; type SamlInfo = { userId: string; @@ -122,30 +117,25 @@ type SAMLProviderOptions = { export function createSamlProvider( _authProviderConfig: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + _env: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { - const envProviders: EnvironmentHandlers = {}; + const entryPoint = envConfig.getString('entryPoint'); + const issuer = envConfig.getString('issuer'); + const opts = { + entryPoint, + issuer, + path: '/auth/saml/handler/frame', + tokenIssuer, + }; - for (const [env, envConfig] of Object.entries(providerConfig)) { - const config = (envConfig as unknown) as SAMLProviderConfig; - const opts = { - entryPoint: config.entryPoint, - issuer: config.issuer, - path: '/auth/saml/handler/frame', - tokenIssuer, - }; - - if (!opts.entryPoint || !opts.issuer) { - logger.warn( - 'SAML auth provider disabled, set entryPoint and entryPoint in saml auth config to enable', - ); - continue; - } - - envProviders[env] = new SamlAuthProvider(opts); + if (!opts.entryPoint || !opts.issuer) { + logger.warn( + 'SAML auth provider disabled, set entryPoint and entryPoint in saml auth config to enable', + ); + return undefined; } - - return new EnvironmentHandler('saml', envProviders); + return new SamlAuthProvider(opts); } diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index de4e076919..93d459bb0d 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -17,6 +17,9 @@ import express from 'express'; import { Logger } from 'winston'; import { TokenIssuer } from '../identity'; +import { Config } from '@backstage/config'; +import { OAuthProvider } from '../lib/OAuthProvider'; +import { SamlAuthProvider } from './saml/provider'; export type OAuthProviderOptions = { /** @@ -204,10 +207,11 @@ export interface AuthProviderRouteHandlers { export type AuthProviderFactory = ( globalConfig: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + env: string, + envConfig: Config, logger: Logger, issuer: TokenIssuer, -) => AuthProviderRouteHandlers; +) => OAuthProvider | SamlAuthProvider | undefined; export type AuthResponse = { providerInfo: ProviderInfo; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 6b296cba2d..69f058f6db 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -36,8 +36,6 @@ export async function createRouter( const router = Router(); const logger = options.logger.child({ plugin: 'auth' }); - const appUrl = new URL(options.config.getString('app.baseUrl')); - const appOrigin = appUrl.origin; const backendUrl = options.config.getString('backend.baseUrl'); const authUrl = `${backendUrl}/auth`; @@ -57,71 +55,13 @@ export async function createRouter( router.use(bodyParser.urlencoded({ extended: false })); router.use(bodyParser.json()); - const config = { - backend: { - baseUrl: backendUrl, - }, - auth: { - providers: { - google: { - development: { - appOrigin, - secure: false, - clientId: process.env.AUTH_GOOGLE_CLIENT_ID!, - clientSecret: process.env.AUTH_GOOGLE_CLIENT_SECRET!, - }, - }, - github: { - development: { - appOrigin, - secure: false, - clientId: process.env.AUTH_GITHUB_CLIENT_ID!, - clientSecret: process.env.AUTH_GITHUB_CLIENT_SECRET!, - }, - }, - gitlab: { - development: { - appOrigin, - secure: false, - clientId: process.env.AUTH_GITLAB_CLIENT_ID!, - clientSecret: process.env.AUTH_GITLAB_CLIENT_SECRET!, - audience: process.env.GITLAB_BASE_URL! || 'https://gitlab.com', - }, - }, - saml: { - development: { - entryPoint: 'http://localhost:7001/', - issuer: 'passport-saml', - }, - }, - okta: { - development: { - appOrigin, - secure: false, - clientId: process.env.AUTH_OKTA_CLIENT_ID!, - clientSecret: process.env.AUTH_OKTA_CLIENT_SECRET!, - audience: process.env.AUTH_OKTA_AUDIENCE, - }, - }, - oauth2: { - development: { - appOrigin, - secure: false, - clientId: process.env.AUTH_OAUTH2_CLIENT_ID!, - clientSecret: process.env.AUTH_OAUTH2_CLIENT_SECRET!, - authorizationURL: process.env.AUTH_OAUTH2_AUTH_URL!, - tokenURL: process.env.AUTH_OAUTH2_TOKEN_URL!, - }, - }, - }, - }, - }; + const providersConfig = options.config.getConfig('auth.providers'); + const providers = providersConfig.keys(); - const providerConfigs = config.auth.providers; - - for (const [providerId, providerConfig] of Object.entries(providerConfigs)) { + for (const providerId of providers) { logger.info(`Configuring provider, ${providerId}`); try { + const providerConfig = providersConfig.getConfig(providerId); const providerRouter = createAuthProviderRouter( providerId, { baseUrl: authUrl }, From 19071023cd96b7b79f7f546fe7f96e056d12f9a9 Mon Sep 17 00:00:00 2001 From: Raghunandan Date: Fri, 17 Jul 2020 14:13:47 +0200 Subject: [PATCH 71/78] fix type issues --- plugins/auth-backend/src/providers/github/provider.ts | 4 ++-- plugins/auth-backend/src/providers/gitlab/provider.ts | 6 +++--- plugins/auth-backend/src/providers/oauth2/provider.ts | 8 ++++---- plugins/auth-backend/src/providers/okta/provider.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index a67e78febd..37d661f9ab 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -134,8 +134,8 @@ export function createGithubProvider( const providerId = 'github'; const secure = envConfig.getBoolean('secure'); const appOrigin = envConfig.getString('appOrigin'); - const clientID = envConfig.getOptionalString('clientId'); - const clientSecret = envConfig.getOptionalString('clientSecret'); + const clientID = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); const callbackURL = `${baseUrl}/${providerId}/handler/frame?env=${env}`; const opts = { diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 78b1a55211..c5c546c6f3 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -141,9 +141,9 @@ export function createGitlabProvider( const providerId = 'gitlab'; const secure = envConfig.getBoolean('secure'); const appOrigin = envConfig.getString('appOrigin'); - const clientID = envConfig.getOptionalString('clientId'); - const clientSecret = envConfig.getOptionalString('clientSecret'); - const audience = envConfig.getOptionalString('audience'); + const clientID = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); + const audience = envConfig.getString('audience'); const baseURL = audience || 'https://gitlab.com'; const callbackURL = `${baseUrl}/${providerId}/handler/frame?env=${env}`; diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index e4258605ac..967fd011f0 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -151,11 +151,11 @@ export function createOAuth2Provider( const providerId = 'oauth2'; const secure = envConfig.getBoolean('secure'); const appOrigin = envConfig.getString('appOrigin'); - const clientID = envConfig.getOptionalString('clientId'); - const clientSecret = envConfig.getOptionalString('clientSecret'); + const clientID = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); const callbackURL = `${baseUrl}/${providerId}/handler/frame?env=${env}`; - const authorizationURL = envConfig.getOptionalString('authorizationURL'); - const tokenURL = envConfig.getOptionalString('tokenURL'); + const authorizationURL = envConfig.getString('authorizationURL'); + const tokenURL = envConfig.getString('tokenURL'); const opts = { clientID, diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 5fcc7cb778..56e8b1220d 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -173,9 +173,9 @@ export function createOktaProvider( const providerId = 'okta'; const secure = envConfig.getBoolean('secure'); const appOrigin = envConfig.getString('appOrigin'); - const clientID = envConfig.getOptionalString('clientId'); - const clientSecret = envConfig.getOptionalString('clientSecret'); - const audience = envConfig.getOptionalString('audience'); + const clientID = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); + const audience = envConfig.getString('audience'); const callbackURL = `${baseUrl}/${providerId}/handler/frame?env=${env}`; const opts = { From 5ead870b44aeebf100c9ba6c185664fe49e5a298 Mon Sep 17 00:00:00 2001 From: Raghunandan Date: Fri, 17 Jul 2020 14:26:21 +0200 Subject: [PATCH 72/78] Add config api to storybook --- packages/app/src/App.tsx | 4 ++-- packages/core/src/layout/Sidebar/UserSettings.tsx | 4 ++-- packages/storybook/.storybook/apis.js | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 497b016c5c..9e9ca1edd0 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -34,8 +34,8 @@ const app = createApp({ components: { SignInPage: props => { const configApi = useApi(configApiRef); - const providersConfig = configApi.getConfig('auth.providers'); - const providers = providersConfig.keys(); + const providersConfig = configApi.getOptionalConfig('auth.providers'); + const providers = providersConfig?.keys() ?? []; return ; }, diff --git a/packages/core/src/layout/Sidebar/UserSettings.tsx b/packages/core/src/layout/Sidebar/UserSettings.tsx index 98362a6b9a..e69d63186d 100644 --- a/packages/core/src/layout/Sidebar/UserSettings.tsx +++ b/packages/core/src/layout/Sidebar/UserSettings.tsx @@ -41,8 +41,8 @@ export function SidebarUserSettings() { const [open, setOpen] = React.useState(false); const identityApi = useApi(identityApiRef); const configApi = useApi(configApiRef); - const providersConfig = configApi.getConfig('auth.providers'); - const providers = providersConfig.keys(); + const providersConfig = configApi.getOptionalConfig('auth.providers'); + const providers = providersConfig?.keys() ?? []; // Close the provider list when sidebar collapse useEffect(() => { diff --git a/packages/storybook/.storybook/apis.js b/packages/storybook/.storybook/apis.js index f4b7557b89..051d95de05 100644 --- a/packages/storybook/.storybook/apis.js +++ b/packages/storybook/.storybook/apis.js @@ -18,10 +18,14 @@ import { OAuthRequestManager, OktaAuth, oktaAuthApiRef, + configApiRef, + ConfigReader, } from '@backstage/core'; const builder = ApiRegistry.builder(); +builder.add(configApiRef, ConfigReader.fromConfigs([])); + const alertApi = builder.add(alertApiRef, new AlertApiForwarder()); builder.add(errorApiRef, new ErrorAlerter(alertApi, new ErrorApiForwarder())); From 166998b6d9762005a6db178006dc1abc0682d186 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 27 Jul 2020 09:17:00 +0200 Subject: [PATCH 73/78] fix(techdocs-not-found): bug fix --- plugins/techdocs/src/reader/components/Reader.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index a6b23bd454..8e11df428e 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -91,6 +91,10 @@ export const Reader = () => { return; // Page isn't ready } + if (state.value instanceof Error) { + return; // Docs not found + } + // Pre-render const transformedElement = transformer(state.value as string, [ sanitizeDOM(), From ab6dfa3aa2a29dd1f546d267bcca49f9c9f17edf Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Mon, 27 Jul 2020 10:42:06 +0200 Subject: [PATCH 74/78] feat: add github icon and more descriptive text --- .../WorkflowRunDetailsPage.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx index 308ae297d8..283fd60ec6 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx @@ -54,6 +54,7 @@ import { useWorkflowRunsDetails } from './useWorkflowRunsDetails'; import { useWorkflowRunJobs } from './useWorkflowRunJobs'; import { WorkflowRunStatusIcon } from '../WorkflowRunStatusIcon/WorkflowRunStatusIcon'; import { useProjectName } from '../useProjectName'; +import GitHubIcon from '@material-ui/icons/GitHub'; const useStyles = makeStyles(theme => ({ root: { @@ -241,9 +242,15 @@ export const WorkflowRunDetailsPage = () => { {details.value?.html_url && ( - + + + )} From 4517f2382dc4c80cc76377a992510fe03a9bcbe3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 27 Jul 2020 10:42:24 +0200 Subject: [PATCH 75/78] packages/cli: avoid packing deps of deps that are marked as bundled --- packages/cli/src/lib/packager/index.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/packager/index.ts b/packages/cli/src/lib/packager/index.ts index d3ba1f6b12..f4a2181b40 100644 --- a/packages/cli/src/lib/packager/index.ts +++ b/packages/cli/src/lib/packager/index.ts @@ -134,11 +134,15 @@ async function findTargetPackages(pkgNames: string[]): Promise { throw new Error(`Package '${name}' not found`); } - const pkgDeps = Object.keys(node.pkg.dependencies); - const localDeps: string[] = Array.from(node.localDependencies.keys()); - const filteredDeps = localDeps.filter(dep => pkgDeps.includes(dep)); + // Don't include dependencies of packages that are marked as bundled + if (!node.pkg.get('bundled')) { + const pkgDeps = Object.keys(node.pkg.dependencies); + const localDeps: string[] = Array.from(node.localDependencies.keys()); + const filteredDeps = localDeps.filter(dep => pkgDeps.includes(dep)); + + searchNames.push(...filteredDeps); + } - searchNames.push(...filteredDeps); targets.set(name, node.pkg); } From 504358df5ad2ec17d8858d50a7f0d8c818bfd117 Mon Sep 17 00:00:00 2001 From: Nikita Nek Dudnik Date: Mon, 27 Jul 2020 11:41:40 +0200 Subject: [PATCH 76/78] docs: add documentation --- plugins/github-actions/README.md | 73 ++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/plugins/github-actions/README.md b/plugins/github-actions/README.md index b0b339a1b0..72f991e814 100644 --- a/plugins/github-actions/README.md +++ b/plugins/github-actions/README.md @@ -1,13 +1,70 @@ -# github-actions +# GitHub Actions Plugin -Welcome to the github-actions plugin! +Website: [https://github.com/actions](https://github.com/actions) -_This plugin was created through the Backstage CLI_ +## Screenshots -## Getting started +TBD -Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/github-actions](http://localhost:3000/github-actions). +## Setup +### Generic Requirements +1. Provide OAuth credentials: + 1. [Create an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) with callback URL set to ```https://localhost:3000/auth/github```. + 2. Take Client ID and Client Secret from the newly created app's settings page and put them into ```AUTH_GITHUB_CLIENT_ID``` and ```AUTH_GITHUB_CLIENT_SECRET``` env variables. +2. Annotate your component with a correct GitHub Actions repository and owner: + + The annotation key is ```backstage.io/github-actions-id```. -You can also serve the plugin in isolation by running `yarn start` in the plugin directory. -This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. + Example: + ``` + apiVersion: backstage.io/v1alpha1 + kind: Component + metadata: + name: backstage + description: backstage.io + annotations: + backstage.io/github-actions-id: 'spotify/backstage' + spec: + type: website + lifecycle: production + owner: guest + ``` +### Standalone app requirements +If you didn't clone this repo you have to do some extra work. + +1. Add plugin API to your Backstage instance: + +```bash +yarn add @backstage/plugin-github-actions +``` + +```js +// packages/app/src/api.ts +import { ApiRegistry } from '@backstage/core'; +import { GithubActionsClient, githubActionsApiRef } from '@backstage/plugin-github-actions'; + +const builder = ApiRegistry.builder(); +builder.add(githubActionsApiRef, new GithubActionsClient()); + +export default builder.build() as ApiHolder; +``` + +2. Add plugin itself: + +```js +// packages/app/src/plugins.ts +export { plugin as GithubActions } from '@backstage/plugin-github-actions'; +``` + +3. Run the app with `yarn start` and the backend with `yarn --cwd packages/backend start`, navigate to `/github-actions/`. + +## Features + +- List workflow runs for a project +- Dive into one run to see a job steps +- Retry runs +- Pagination for runs + +## Limitations + +- There is a limit of 100 apps for one OAuth client/token pair From 2aacd157ca60f570f789ddad63dcf7352676f350 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 27 Jul 2020 12:58:02 +0200 Subject: [PATCH 77/78] docgen: bump to alpha.16 --- packages/docgen/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docgen/package.json b/packages/docgen/package.json index f2675eef24..97d057a4b7 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.13", + "version": "0.1.1-alpha.16", "private": true, "homepage": "https://backstage.io", "repository": { From bd62f980700ff3f0072e4f2a5661cf2d59c4df45 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Mon, 27 Jul 2020 07:46:33 -0400 Subject: [PATCH 78/78] fix: logger comment for errorhandler options --- packages/backend-common/src/middleware/errorHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index 798d173d38..7365ce8b93 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -28,9 +28,9 @@ export type ErrorHandlerOptions = { showStackTraces?: boolean; /** - * Logger + * Logger instance to log any 5xx errors. * - * If not specified, by default shows stack traces only in development mode. + * If not specified, the root logger will be used. */ logger?: Logger; };