From 1fb616c5f5398c905936df23f2431a08f7305882 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 27 Jul 2020 15:36:28 +0200 Subject: [PATCH 001/219] feat(gql): added some experimentation around graphql modules --- plugins/bogus-graphql/.eslintrc.js | 3 + plugins/bogus-graphql/README.md | 13 ++ plugins/bogus-graphql/package.json | 44 +++++ plugins/bogus-graphql/scripts/mock-data.sh | 2 + plugins/bogus-graphql/src/graphql/module.ts | 47 +++++ plugins/bogus-graphql/src/index.ts | 19 ++ plugins/bogus-graphql/src/schema.gql | 3 + plugins/bogus-graphql/src/setupTests.ts | 18 ++ plugins/catalog-graphql/.eslintrc.js | 3 + plugins/catalog-graphql/README.md | 13 ++ plugins/catalog-graphql/package.json | 44 +++++ plugins/catalog-graphql/scripts/mock-data.sh | 2 + plugins/catalog-graphql/src/graphql/module.ts | 82 +++++++++ plugins/catalog-graphql/src/index.ts | 19 ++ plugins/catalog-graphql/src/schema.gql | 25 +++ plugins/catalog-graphql/src/setupTests.ts | 18 ++ plugins/graphql/package.json | 3 + plugins/graphql/src/schema.gql | 11 -- plugins/graphql/src/service/router.ts | 12 +- yarn.lock | 166 +++++++++++++++++- 20 files changed, 528 insertions(+), 19 deletions(-) create mode 100644 plugins/bogus-graphql/.eslintrc.js create mode 100644 plugins/bogus-graphql/README.md create mode 100644 plugins/bogus-graphql/package.json create mode 100755 plugins/bogus-graphql/scripts/mock-data.sh create mode 100644 plugins/bogus-graphql/src/graphql/module.ts create mode 100644 plugins/bogus-graphql/src/index.ts create mode 100644 plugins/bogus-graphql/src/schema.gql create mode 100644 plugins/bogus-graphql/src/setupTests.ts create mode 100644 plugins/catalog-graphql/.eslintrc.js create mode 100644 plugins/catalog-graphql/README.md create mode 100644 plugins/catalog-graphql/package.json create mode 100755 plugins/catalog-graphql/scripts/mock-data.sh create mode 100644 plugins/catalog-graphql/src/graphql/module.ts create mode 100644 plugins/catalog-graphql/src/index.ts create mode 100644 plugins/catalog-graphql/src/schema.gql create mode 100644 plugins/catalog-graphql/src/setupTests.ts diff --git a/plugins/bogus-graphql/.eslintrc.js b/plugins/bogus-graphql/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/bogus-graphql/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/bogus-graphql/README.md b/plugins/bogus-graphql/README.md new file mode 100644 index 0000000000..3f85ec766e --- /dev/null +++ b/plugins/bogus-graphql/README.md @@ -0,0 +1,13 @@ +# 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/bogus-graphql/package.json b/plugins/bogus-graphql/package.json new file mode 100644 index 0000000000..a5a439a701 --- /dev/null +++ b/plugins/bogus-graphql/package.json @@ -0,0 +1,44 @@ +{ + "name": "@backstage/plugin-bogus-graphql", + "version": "0.1.1-alpha.16", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" + }, + "dependencies": { + "@backstage/backend-common": "^0.1.1-alpha.16", + "@graphql-modules/core": "^0.7.17", + "@types/express": "^4.17.6", + "@types/whatwg-fetch": "^0.0.33", + "graphql": "^15.3.0", + "node-fetch": "^2.6.0", + "whatwg-fetch": "^2.0.0", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.16", + "@types/supertest": "^2.0.8", + "eslint-plugin-graphql": "^4.0.0", + "msw": "^0.19.5", + "supertest": "^4.0.2" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/bogus-graphql/scripts/mock-data.sh b/plugins/bogus-graphql/scripts/mock-data.sh new file mode 100755 index 0000000000..ff30921715 --- /dev/null +++ b/plugins/bogus-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/bogus-graphql/src/graphql/module.ts b/plugins/bogus-graphql/src/graphql/module.ts new file mode 100644 index 0000000000..f247686ea8 --- /dev/null +++ b/plugins/bogus-graphql/src/graphql/module.ts @@ -0,0 +1,47 @@ +/* + * 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 fs from 'fs'; +import path from 'path'; +import { GraphQLModule } from '@graphql-modules/core'; + +export interface ModuleOptions { + logger: Logger; +} + +export async function createModule( + options: ModuleOptions, +): Promise { + const typeDefs = await fs.promises.readFile( + path.resolve(__dirname, '..', 'schema.gql'), + 'utf-8', + ); + + const resolvers = { + EntityMetadata: { + uppercaseName: metadata => metadata.name.toUpperCase(), + }, + }; + + const module = new GraphQLModule({ + typeDefs, + resolvers, + logger: options.logger as any, + }); + + return module; +} diff --git a/plugins/bogus-graphql/src/index.ts b/plugins/bogus-graphql/src/index.ts new file mode 100644 index 0000000000..dd6508f88a --- /dev/null +++ b/plugins/bogus-graphql/src/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +require('whatwg-fetch'); + +export * from './graphql/module'; diff --git a/plugins/bogus-graphql/src/schema.gql b/plugins/bogus-graphql/src/schema.gql new file mode 100644 index 0000000000..74c0853a92 --- /dev/null +++ b/plugins/bogus-graphql/src/schema.gql @@ -0,0 +1,3 @@ +type EntityMetadata { + uppercaseName: String +} diff --git a/plugins/bogus-graphql/src/setupTests.ts b/plugins/bogus-graphql/src/setupTests.ts new file mode 100644 index 0000000000..ac41ef995a --- /dev/null +++ b/plugins/bogus-graphql/src/setupTests.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +require('whatwg-fetch'); + +export {}; diff --git a/plugins/catalog-graphql/.eslintrc.js b/plugins/catalog-graphql/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/catalog-graphql/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/catalog-graphql/README.md b/plugins/catalog-graphql/README.md new file mode 100644 index 0000000000..3f85ec766e --- /dev/null +++ b/plugins/catalog-graphql/README.md @@ -0,0 +1,13 @@ +# 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/catalog-graphql/package.json b/plugins/catalog-graphql/package.json new file mode 100644 index 0000000000..57fade9654 --- /dev/null +++ b/plugins/catalog-graphql/package.json @@ -0,0 +1,44 @@ +{ + "name": "@backstage/plugin-catalog-graphql", + "version": "0.1.1-alpha.16", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" + }, + "dependencies": { + "@backstage/backend-common": "^0.1.1-alpha.16", + "@graphql-modules/core": "^0.7.17", + "@types/express": "^4.17.6", + "@types/whatwg-fetch": "^0.0.33", + "graphql": "^15.3.0", + "node-fetch": "^2.6.0", + "whatwg-fetch": "^2.0.0", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.16", + "@types/supertest": "^2.0.8", + "eslint-plugin-graphql": "^4.0.0", + "msw": "^0.19.5", + "supertest": "^4.0.2" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/catalog-graphql/scripts/mock-data.sh b/plugins/catalog-graphql/scripts/mock-data.sh new file mode 100755 index 0000000000..ff30921715 --- /dev/null +++ b/plugins/catalog-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/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts new file mode 100644 index 0000000000..65239d27db --- /dev/null +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -0,0 +1,82 @@ +/* + * 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 fetch from 'node-fetch'; +import fs from 'fs'; +import path from 'path'; +import { GraphQLModule } from '@graphql-modules/core'; + +export interface ModuleOptions { + logger: Logger; +} + +// function wrapLogger(logger: Logger) { +// return { +// log: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// debug: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// info: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// error: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// clientError: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// warn: (msg: string | Error, ...extra: any[]) => +// logger.info(String(msg), ...extra), +// }; +// } + +class CatalogClient { + async list() { + const res = await fetch('http://localhost:7000/catalog/entities'); + if (!res.ok) { + throw new Error(`NOPE, ${await res.text()}`); + } + + return res.json(); + } +} + +export async function createModule( + options: ModuleOptions, +): Promise { + const typeDefs = await fs.promises.readFile( + path.resolve(__dirname, '..', 'schema.gql'), + 'utf-8', + ); + + const catalogClient = new CatalogClient(); + + const resolvers = { + Query: { + catalog: () => true, + }, + CatalogQuery: { + list: () => catalogClient.list(), + }, + CatalogEntity: () => ({}), + }; + + const module = new GraphQLModule({ + typeDefs, + resolvers, + logger: options.logger as any, + }); + + return module; +} diff --git a/plugins/catalog-graphql/src/index.ts b/plugins/catalog-graphql/src/index.ts new file mode 100644 index 0000000000..dd6508f88a --- /dev/null +++ b/plugins/catalog-graphql/src/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +require('whatwg-fetch'); + +export * from './graphql/module'; diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/src/schema.gql new file mode 100644 index 0000000000..ea92d4e04f --- /dev/null +++ b/plugins/catalog-graphql/src/schema.gql @@ -0,0 +1,25 @@ +type EntityMetadataAnnotation { + key: String! + value: String! +} + +type EntityMetadata { + name: String! + namespace: String + annotation(name: String): EntityMetadataAnnotation + annotations: [EntityMetadataAnnotation!]! +} + +type CatalogEntity { + apiVersion: String + kind: String! + metadata: EntityMetadata +} + +type CatalogQuery { + list: [CatalogEntity!]! +} + +type Query { + catalog: CatalogQuery! +} diff --git a/plugins/catalog-graphql/src/setupTests.ts b/plugins/catalog-graphql/src/setupTests.ts new file mode 100644 index 0000000000..ac41ef995a --- /dev/null +++ b/plugins/catalog-graphql/src/setupTests.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +require('whatwg-fetch'); + +export {}; diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 42f3dc9046..269ebe71f5 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -22,6 +22,9 @@ }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.16", + "@backstage/plugin-catalog-graphql": "^0.1.1-alpha.16", + "@backstage/plugin-bogus-graphql": "^0.1.1-alpha.16", + "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", "apollo-server": "^2.16.0", "apollo-server-express": "^2.16.0", diff --git a/plugins/graphql/src/schema.gql b/plugins/graphql/src/schema.gql index 5568182f7f..e69de29bb2 100644 --- a/plugins/graphql/src/schema.gql +++ b/plugins/graphql/src/schema.gql @@ -1,11 +0,0 @@ -type CatalogEntity { - id: String -} - -type CatalogQuery { - list: [CatalogEntity!]! -} - -type Query { - catalog: CatalogQuery! -} diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts index 09ab5270b7..e12f3770dd 100644 --- a/plugins/graphql/src/service/router.ts +++ b/plugins/graphql/src/service/router.ts @@ -20,7 +20,10 @@ import Router from 'express-promise-router'; import { Logger } from 'winston'; import fs from 'fs'; import path from 'path'; +import { GraphQLModule } from '@graphql-modules/core'; import { ApolloServer } from 'apollo-server-express'; +import { createModule as createCatalogModule } from '@backstage/plugin-catalog-graphql'; +import { createModule as createBogusModule } from '@backstage/plugin-bogus-graphql'; export interface RouterOptions { logger: Logger; @@ -34,7 +37,14 @@ export async function createRouter( 'utf-8', ); - const server = new ApolloServer({ typeDefs, logger: options.logger }); + const catalogModule = await createCatalogModule(options); + const bogusModule = await createBogusModule(options); + const { schema } = new GraphQLModule({ + imports: [catalogModule, bogusModule], + typeDefs, + }); + + const server = new ApolloServer({ schema, logger: options.logger }); const router = Router(); const apolloMiddlware = server.getMiddleware({ path: '/' }); diff --git a/yarn.lock b/yarn.lock index a31c2a75ae..55d8356bed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1229,6 +1229,47 @@ unique-filename "^1.1.1" which "^1.3.1" +"@graphql-modules/core@^0.7.17": + version "0.7.17" + resolved "https://registry.npmjs.org/@graphql-modules/core/-/core-0.7.17.tgz#ae9bbf44894509664bc0b6fcdd9b6cf3d99bcd00" + integrity sha512-hGJa1VIsIHTKJ0Hc5gJfFrdhHAF1Vm+LWYeMNC5mPbVd/IZA1wVSpdLDjjliylLI6GnVRu1YreS2gXvFNXPJFA== + dependencies: + "@graphql-modules/di" "0.7.17" + "@graphql-toolkit/common" "0.10.6" + "@graphql-toolkit/schema-merging" "0.10.6" + apollo-server-caching "0.5.1" + deepmerge "4.2.2" + graphql-tools "5.0.0" + tslib "2.0.0" + +"@graphql-modules/di@0.7.17": + version "0.7.17" + resolved "https://registry.npmjs.org/@graphql-modules/di/-/di-0.7.17.tgz#049f93f204a6d90b7ee4e047a0654c6056f28065" + integrity sha512-Lq5sd/3RO+bNb8wVnAg43LWbwrqD57D0AVEqZlqiTwUj1f0mITtQdGMRN7g2sG79U7ngoaQx8VK/IiKh8E1OFQ== + dependencies: + events "3.1.0" + tslib "2.0.0" + +"@graphql-toolkit/common@0.10.6": + version "0.10.6" + resolved "https://registry.npmjs.org/@graphql-toolkit/common/-/common-0.10.6.tgz#43591fd3478ab27ec95bf39d5a8afdd17f0ac2fd" + integrity sha512-rrH/KPheh/wCZzqUmNayBHd+aNWl/751C4iTL/327TzONdAVrV7ZQOyEkpGLW6YEFWPIlWxNkaBoEALIjCxTGg== + dependencies: + aggregate-error "3.0.1" + camel-case "4.1.1" + graphql-tools "5.0.0" + lodash "4.17.15" + +"@graphql-toolkit/schema-merging@0.10.6": + version "0.10.6" + resolved "https://registry.npmjs.org/@graphql-toolkit/schema-merging/-/schema-merging-0.10.6.tgz#9f57a349621a4377a3431a0320221d9aa6a9d982" + integrity sha512-BNABgYaNCw4Li3EiH/x7oDpkN+ml3M0SWqjnsW1Pf2NcyfGlv033Bda+O/q4XYtseZ0OOOh52GLXtUgwyPFb8A== + dependencies: + "@graphql-toolkit/common" "0.10.6" + deepmerge "4.2.2" + graphql-tools "5.0.0" + tslib "1.11.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" @@ -4472,6 +4513,20 @@ dependencies: "@types/node" "*" +"@types/whatwg-fetch@^0.0.33": + version "0.0.33" + resolved "https://registry.npmjs.org/@types/whatwg-fetch/-/whatwg-fetch-0.0.33.tgz#19c0d2863c8cb2380f21a1c736b79cbf7895bb13" + integrity sha1-GcDShjyMsjgPIaHHNrecv3iVuxM= + dependencies: + "@types/whatwg-streams" "*" + +"@types/whatwg-streams@*": + version "3.2.1" + resolved "https://registry.npmjs.org/@types/whatwg-streams/-/whatwg-streams-3.2.1.tgz#225009a823e8b44be12569a24e60374ccdabe29e" + integrity sha512-Syv05sRL25b8cC8tqgXSQgLZZmqGq2GO+NafrtHbjPJccP6gWBXmHvo2Trw3AWXQ4QLIkVuOB7uStCuhzswyiw== + dependencies: + whatwg-streams "*" + "@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" @@ -5056,7 +5111,16 @@ apollo-graphql@^0.5.0: apollo-env "^0.6.5" lodash.sortby "^4.7.0" -apollo-link@^1.2.14: +apollo-link-http-common@^0.2.14: + version "0.2.16" + resolved "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz#756749dafc732792c8ca0923f9a40564b7c59ecc" + integrity sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg== + dependencies: + apollo-link "^1.2.14" + ts-invariant "^0.4.0" + tslib "^1.9.3" + +apollo-link@^1.2.12, 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== @@ -5066,6 +5130,13 @@ apollo-link@^1.2.14: tslib "^1.9.3" zen-observable-ts "^0.8.21" +apollo-server-caching@0.5.1: + version "0.5.1" + resolved "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.1.tgz#5cd0536ad5473abb667cc82b59bc56b96fb35db6" + integrity sha512-L7LHZ3k9Ao5OSf2WStvQhxdsNVplRQi7kCAPfqf9Z3GBEnQ2uaL0EgO0hSmtVHfXTbk5CTRziMT1Pe87bXrFIw== + dependencies: + lru-cache "^5.0.0" + 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" @@ -5171,6 +5242,16 @@ apollo-tracing@^0.11.1: apollo-server-env "^2.4.5" apollo-server-plugin-base "^0.9.1" +apollo-upload-client@^13.0.0: + version "13.0.0" + resolved "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-13.0.0.tgz#146d1ddd85d711fcac8ca97a72d3ca6787f2b71b" + integrity sha512-lJ9/bk1BH1lD15WhWRha2J3+LrXrPIX5LP5EwiOUHv8PCORp4EUrcujrA3rI5hZeZygrTX8bshcuMdpqpSrvtA== + dependencies: + "@babel/runtime" "^7.9.2" + apollo-link "^1.2.12" + apollo-link-http-common "^0.2.14" + extract-files "^8.0.0" + 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" @@ -5954,6 +6035,11 @@ bluebird@3.7.2, bluebird@^3.3.5, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5 resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bluebird@~1.0.0: + version "1.0.8" + resolved "https://registry.npmjs.org/bluebird/-/bluebird-1.0.8.tgz#851c7825e6cce59e4b43dde95d574b88675463fc" + integrity sha1-hRx4JebM5Z5LQ93pXVdLiGdUY/w= + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -7885,6 +7971,11 @@ deep-equal@^1.0.1, deep-equal@^1.1.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" +deep-equal@~0.1.0: + version "0.1.2" + resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz#b246c2b80a570a47c11be1d9bd1070ec878b87ce" + integrity sha1-skbCuApXCkfBG+HZvRBw7IeLh84= + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -7905,7 +7996,7 @@ deep-object-diff@^1.1.0: resolved "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a" integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw== -deepmerge@^4.2.2: +deepmerge@4.2.2, deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== @@ -7959,6 +8050,11 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +defined@~0.0.0: + version "0.0.0" + resolved "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz#f35eea7d705e933baf13b2f03b3f83d921403b3e" + integrity sha1-817qfXBekzuvE7LwOz+D2SFAOz4= + del@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" @@ -9007,7 +9103,7 @@ eventemitter3@^4.0.0: resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg== -events@^3.0.0: +events@3.1.0, events@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== @@ -9225,6 +9321,11 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-files@^8.0.0: + version "8.1.0" + resolved "https://registry.npmjs.org/extract-files/-/extract-files-8.1.0.tgz#46a0690d0fe77411a2e3804852adeaa65cd59288" + integrity sha512-PTGtfthZK79WUMk+avLmwx3NGdU8+iVFXC2NMGxKsn0MnihOG2lvumj+AZo8CTwTrwjXDgZ5tztbRlEdRjBonQ== + extract-zip@1.7.0: version "1.7.0" resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" @@ -10367,6 +10468,20 @@ graphql-tag@^2.9.2: resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.4.tgz#2f301a98219be8b178a6453bb7e33b79b66d8f83" integrity sha512-O7vG5BT3w6Sotc26ybcvLKNTdfr4GfsIVMD+LdYqXCeJIYPRyp8BIsDOUtxw7S1PYvRw5vH3278J2EDezR6mfA== +graphql-tools@5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/graphql-tools/-/graphql-tools-5.0.0.tgz#67281c834a0e29f458adba8018f424816fa627e9" + integrity sha512-5zn3vtn//382b7G3Wzz3d5q/sh+f7tVrnxeuhTMTJ7pWJijNqLxH7VEzv8VwXCq19zAzHYEosFHfXiK7qzvk7w== + dependencies: + apollo-link "^1.2.14" + apollo-upload-client "^13.0.0" + deprecated-decorator "^0.1.6" + form-data "^3.0.0" + iterall "^1.3.0" + node-fetch "^2.6.0" + tslib "^1.11.1" + uuid "^7.0.3" + graphql-tools@^4.0.0: version "4.0.8" resolved "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz#e7fb9f0d43408fb0878ba66b522ce871bafe9d30" @@ -11867,7 +11982,7 @@ 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: +iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2, iterall@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== @@ -12508,6 +12623,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" @@ -17135,6 +17255,13 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +resumer@~0.0.0: + version "0.0.0" + resolved "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" + integrity sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k= + dependencies: + through "~2.3.4" + ret@~0.1.10: version "0.1.15" resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -18664,6 +18791,18 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tape@~2.3.2: + version "2.3.3" + resolved "https://registry.npmjs.org/tape/-/tape-2.3.3.tgz#2e7ce0a31df09f8d6851664a71842e0ca5057af7" + integrity sha1-Lnzgox3wn41oUWZKcYQuDKUFevc= + dependencies: + deep-equal "~0.1.0" + defined "~0.0.0" + inherits "~2.0.1" + jsonify "~0.0.0" + resumer "~0.0.0" + through "~2.3.4" + tar-fs@^1.16.3: version "1.16.3" resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" @@ -18906,7 +19045,7 @@ through2@^3.0.0: dependencies: readable-stream "2 || 3" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1: +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1, through@~2.3.4: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -19207,16 +19346,21 @@ tslib@1.10.0: resolved "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== -tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@1.11.1, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.11.1" 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: +tslib@2.0.0, 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== +tslib@^1.11.1: + version "1.13.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -20084,6 +20228,14 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-streams@*: + version "0.1.1" + resolved "https://registry.npmjs.org/whatwg-streams/-/whatwg-streams-0.1.1.tgz#dd5575c965bce13da76dc0dd495629a87572e74e" + integrity sha1-3VV1yWW84T2nbcDdSVYpqHVy504= + dependencies: + bluebird "~1.0.0" + tape "~2.3.2" + whatwg-url@^6.4.1: version "6.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" From 7811b086ebb4d9081ac4cd26d627228b5015c5c2 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 4 Aug 2020 09:46:56 +0200 Subject: [PATCH 002/219] chore(gql): added some nice stuff with the graphql context --- plugins/catalog-graphql/codegen.yml | 15 + plugins/catalog-graphql/package.json | 5 +- .../catalog-graphql/src/generated-models.ts | 288 ++++++++++++++++++ plugins/catalog-graphql/src/graphql/module.ts | 1 + plugins/catalog-graphql/src/index.ts | 2 - plugins/catalog-graphql/src/setupTests.ts | 2 - 6 files changed, 308 insertions(+), 5 deletions(-) create mode 100644 plugins/catalog-graphql/codegen.yml create mode 100644 plugins/catalog-graphql/src/generated-models.ts diff --git a/plugins/catalog-graphql/codegen.yml b/plugins/catalog-graphql/codegen.yml new file mode 100644 index 0000000000..c9e0173256 --- /dev/null +++ b/plugins/catalog-graphql/codegen.yml @@ -0,0 +1,15 @@ +overwrite: true +schema: ./src/schema.gql # You can use .js files as well +require: + - ts-node/register/transpile-only # required if you're using TS-Node +generates: + ./src/generated-models.ts: + plugins: + - typescript + - typescript-resolvers + + hooks: + afterOneFileWrite: + - prettier --write + config: + contextType: '@graphql-modules/core#ModuleContext' diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 57fade9654..8c478e767b 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -22,12 +22,15 @@ }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.16", + "@graphql-codegen/cli": "^1.17.7", + "@graphql-codegen/typescript": "^1.17.7", + "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", "@types/whatwg-fetch": "^0.0.33", "graphql": "^15.3.0", "node-fetch": "^2.6.0", - "whatwg-fetch": "^2.0.0", + "ts-node": "^8.10.2", "winston": "^3.2.1", "yn": "^4.0.0" }, diff --git a/plugins/catalog-graphql/src/generated-models.ts b/plugins/catalog-graphql/src/generated-models.ts new file mode 100644 index 0000000000..f0f7f2f372 --- /dev/null +++ b/plugins/catalog-graphql/src/generated-models.ts @@ -0,0 +1,288 @@ +/* + * 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 { GraphQLResolveInfo } from 'graphql'; +import { ModuleContext } from '@graphql-modules/core'; + +export type Maybe = T | null; +export type Exact = { + [K in keyof T]: T[K]; +}; +export type RequireFields = { + [X in Exclude]?: T[X]; +} & + { [P in K]-?: NonNullable }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; +}; + +export type EntityMetadataAnnotation = { + __typename?: 'EntityMetadataAnnotation'; + key: Scalars['String']; + value: Scalars['String']; +}; + +export type EntityMetadata = { + __typename?: 'EntityMetadata'; + name: Scalars['String']; + namespace?: Maybe; + annotation?: Maybe; + annotations: Array; +}; + +export type EntityMetadataAnnotationArgs = { + name?: Maybe; +}; + +export type CatalogEntity = { + __typename?: 'CatalogEntity'; + apiVersion?: Maybe; + kind: Scalars['String']; + metadata?: Maybe; +}; + +export type CatalogQuery = { + __typename?: 'CatalogQuery'; + list: Array; +}; + +export type Query = { + __typename?: 'Query'; + catalog: CatalogQuery; +}; + +export type ResolverTypeWrapper = Promise | T; + +export type LegacyStitchingResolver = { + fragment: string; + resolve: ResolverFn; +}; + +export type NewStitchingResolver = { + selectionSet: string; + resolve: ResolverFn; +}; +export type StitchingResolver = + | LegacyStitchingResolver + | NewStitchingResolver; +export type Resolver = + | ResolverFn + | StitchingResolver; + +export type ResolverFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo, +) => Promise | TResult; + +export type SubscriptionSubscribeFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo, +) => AsyncIterator | Promise>; + +export type SubscriptionResolveFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo, +) => TResult | Promise; + +export interface SubscriptionSubscriberObject< + TResult, + TKey extends string, + TParent, + TContext, + TArgs +> { + subscribe: SubscriptionSubscribeFn< + { [key in TKey]: TResult }, + TParent, + TContext, + TArgs + >; + resolve?: SubscriptionResolveFn< + TResult, + { [key in TKey]: TResult }, + TContext, + TArgs + >; +} + +export interface SubscriptionResolverObject { + subscribe: SubscriptionSubscribeFn; + resolve: SubscriptionResolveFn; +} + +export type SubscriptionObject< + TResult, + TKey extends string, + TParent, + TContext, + TArgs +> = + | SubscriptionSubscriberObject + | SubscriptionResolverObject; + +export type SubscriptionResolver< + TResult, + TKey extends string, + TParent = {}, + TContext = {}, + TArgs = {} +> = + | (( + ...args: any[] + ) => SubscriptionObject) + | SubscriptionObject; + +export type TypeResolveFn = ( + parent: TParent, + context: TContext, + info: GraphQLResolveInfo, +) => Maybe | Promise>; + +export type IsTypeOfResolverFn = ( + obj: T, + info: GraphQLResolveInfo, +) => boolean | Promise; + +export type NextResolverFn = () => Promise; + +export type DirectiveResolverFn< + TResult = {}, + TParent = {}, + TContext = {}, + TArgs = {} +> = ( + next: NextResolverFn, + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo, +) => TResult | Promise; + +/** Mapping between all available schema types and the resolvers types */ +export type ResolversTypes = { + EntityMetadataAnnotation: ResolverTypeWrapper; + String: ResolverTypeWrapper; + EntityMetadata: ResolverTypeWrapper; + CatalogEntity: ResolverTypeWrapper; + CatalogQuery: ResolverTypeWrapper; + Query: ResolverTypeWrapper<{}>; + Boolean: ResolverTypeWrapper; +}; + +/** Mapping between all available schema types and the resolvers parents */ +export type ResolversParentTypes = { + EntityMetadataAnnotation: EntityMetadataAnnotation; + String: Scalars['String']; + EntityMetadata: EntityMetadata; + CatalogEntity: CatalogEntity; + CatalogQuery: CatalogQuery; + Query: {}; + Boolean: Scalars['Boolean']; +}; + +export type EntityMetadataAnnotationResolvers< + ContextType = ModuleContext, + ParentType extends ResolversParentTypes['EntityMetadataAnnotation'] = ResolversParentTypes['EntityMetadataAnnotation'] +> = { + key?: Resolver; + value?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type EntityMetadataResolvers< + ContextType = ModuleContext, + ParentType extends ResolversParentTypes['EntityMetadata'] = ResolversParentTypes['EntityMetadata'] +> = { + name?: Resolver; + namespace?: Resolver< + Maybe, + ParentType, + ContextType + >; + annotation?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; + annotations?: Resolver< + Array, + ParentType, + ContextType + >; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type CatalogEntityResolvers< + ContextType = ModuleContext, + ParentType extends ResolversParentTypes['CatalogEntity'] = ResolversParentTypes['CatalogEntity'] +> = { + apiVersion?: Resolver< + Maybe, + ParentType, + ContextType + >; + kind?: Resolver; + metadata?: Resolver< + Maybe, + ParentType, + ContextType + >; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type CatalogQueryResolvers< + ContextType = ModuleContext, + ParentType extends ResolversParentTypes['CatalogQuery'] = ResolversParentTypes['CatalogQuery'] +> = { + list?: Resolver< + Array, + ParentType, + ContextType + >; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type QueryResolvers< + ContextType = ModuleContext, + ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query'] +> = { + catalog?: Resolver; +}; + +export type Resolvers = { + EntityMetadataAnnotation?: EntityMetadataAnnotationResolvers; + EntityMetadata?: EntityMetadataResolvers; + CatalogEntity?: CatalogEntityResolvers; + CatalogQuery?: CatalogQueryResolvers; + Query?: QueryResolvers; +}; + +/** + * @deprecated + * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. + */ +export type IResolvers = Resolvers; diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 65239d27db..7648b7d302 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -19,6 +19,7 @@ import fetch from 'node-fetch'; import fs from 'fs'; import path from 'path'; import { GraphQLModule } from '@graphql-modules/core'; +import { QueryResolvers } from './types'; export interface ModuleOptions { logger: Logger; diff --git a/plugins/catalog-graphql/src/index.ts b/plugins/catalog-graphql/src/index.ts index dd6508f88a..2e3f1c1fe2 100644 --- a/plugins/catalog-graphql/src/index.ts +++ b/plugins/catalog-graphql/src/index.ts @@ -14,6 +14,4 @@ * limitations under the License. */ -require('whatwg-fetch'); - export * from './graphql/module'; diff --git a/plugins/catalog-graphql/src/setupTests.ts b/plugins/catalog-graphql/src/setupTests.ts index ac41ef995a..1485402d0d 100644 --- a/plugins/catalog-graphql/src/setupTests.ts +++ b/plugins/catalog-graphql/src/setupTests.ts @@ -13,6 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -require('whatwg-fetch'); - export {}; From 9bd1db1ed161621cde965bee0244a6a85a2c63a6 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Aug 2020 07:26:44 +0200 Subject: [PATCH 003/219] chore(gql): removing old graphql things --- plugins/bogus-graphql/.eslintrc.js | 3 -- plugins/bogus-graphql/README.md | 13 ------ plugins/bogus-graphql/package.json | 44 ------------------- plugins/bogus-graphql/scripts/mock-data.sh | 2 - plugins/bogus-graphql/src/graphql/module.ts | 47 --------------------- plugins/bogus-graphql/src/index.ts | 19 --------- plugins/bogus-graphql/src/schema.gql | 3 -- plugins/bogus-graphql/src/setupTests.ts | 18 -------- 8 files changed, 149 deletions(-) delete mode 100644 plugins/bogus-graphql/.eslintrc.js delete mode 100644 plugins/bogus-graphql/README.md delete mode 100644 plugins/bogus-graphql/package.json delete mode 100755 plugins/bogus-graphql/scripts/mock-data.sh delete mode 100644 plugins/bogus-graphql/src/graphql/module.ts delete mode 100644 plugins/bogus-graphql/src/index.ts delete mode 100644 plugins/bogus-graphql/src/schema.gql delete mode 100644 plugins/bogus-graphql/src/setupTests.ts diff --git a/plugins/bogus-graphql/.eslintrc.js b/plugins/bogus-graphql/.eslintrc.js deleted file mode 100644 index 16a033dbc6..0000000000 --- a/plugins/bogus-graphql/.eslintrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: [require.resolve('@backstage/cli/config/eslint.backend')], -}; diff --git a/plugins/bogus-graphql/README.md b/plugins/bogus-graphql/README.md deleted file mode 100644 index 3f85ec766e..0000000000 --- a/plugins/bogus-graphql/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# 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/bogus-graphql/package.json b/plugins/bogus-graphql/package.json deleted file mode 100644 index a5a439a701..0000000000 --- a/plugins/bogus-graphql/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@backstage/plugin-bogus-graphql", - "version": "0.1.1-alpha.16", - "main": "src/index.ts", - "types": "src/index.ts", - "license": "Apache-2.0", - "private": true, - "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" - }, - "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean", - "mock-data": "./scripts/mock-data.sh" - }, - "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.16", - "@graphql-modules/core": "^0.7.17", - "@types/express": "^4.17.6", - "@types/whatwg-fetch": "^0.0.33", - "graphql": "^15.3.0", - "node-fetch": "^2.6.0", - "whatwg-fetch": "^2.0.0", - "winston": "^3.2.1", - "yn": "^4.0.0" - }, - "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.16", - "@types/supertest": "^2.0.8", - "eslint-plugin-graphql": "^4.0.0", - "msw": "^0.19.5", - "supertest": "^4.0.2" - }, - "files": [ - "dist" - ] -} diff --git a/plugins/bogus-graphql/scripts/mock-data.sh b/plugins/bogus-graphql/scripts/mock-data.sh deleted file mode 100755 index ff30921715..0000000000 --- a/plugins/bogus-graphql/scripts/mock-data.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -echo "use this script to load your service with some mock data if needed!" diff --git a/plugins/bogus-graphql/src/graphql/module.ts b/plugins/bogus-graphql/src/graphql/module.ts deleted file mode 100644 index f247686ea8..0000000000 --- a/plugins/bogus-graphql/src/graphql/module.ts +++ /dev/null @@ -1,47 +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 { Logger } from 'winston'; -import fs from 'fs'; -import path from 'path'; -import { GraphQLModule } from '@graphql-modules/core'; - -export interface ModuleOptions { - logger: Logger; -} - -export async function createModule( - options: ModuleOptions, -): Promise { - const typeDefs = await fs.promises.readFile( - path.resolve(__dirname, '..', 'schema.gql'), - 'utf-8', - ); - - const resolvers = { - EntityMetadata: { - uppercaseName: metadata => metadata.name.toUpperCase(), - }, - }; - - const module = new GraphQLModule({ - typeDefs, - resolvers, - logger: options.logger as any, - }); - - return module; -} diff --git a/plugins/bogus-graphql/src/index.ts b/plugins/bogus-graphql/src/index.ts deleted file mode 100644 index dd6508f88a..0000000000 --- a/plugins/bogus-graphql/src/index.ts +++ /dev/null @@ -1,19 +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. - */ - -require('whatwg-fetch'); - -export * from './graphql/module'; diff --git a/plugins/bogus-graphql/src/schema.gql b/plugins/bogus-graphql/src/schema.gql deleted file mode 100644 index 74c0853a92..0000000000 --- a/plugins/bogus-graphql/src/schema.gql +++ /dev/null @@ -1,3 +0,0 @@ -type EntityMetadata { - uppercaseName: String -} diff --git a/plugins/bogus-graphql/src/setupTests.ts b/plugins/bogus-graphql/src/setupTests.ts deleted file mode 100644 index ac41ef995a..0000000000 --- a/plugins/bogus-graphql/src/setupTests.ts +++ /dev/null @@ -1,18 +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. - */ -require('whatwg-fetch'); - -export {}; From de1ddc1096d7253b952aca10fca064433f6b3d86 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Aug 2020 07:27:07 +0200 Subject: [PATCH 004/219] chore(gql): pass through the config for the catalog client --- plugins/graphql/package.json | 2 +- plugins/graphql/src/service/router.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index bc5004c580..8c90c74440 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -21,8 +21,8 @@ }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.16", + "@backstage/config": "^0.1.1-alpha.16", "@backstage/plugin-catalog-graphql": "^0.1.1-alpha.16", - "@backstage/plugin-bogus-graphql": "^0.1.1-alpha.16", "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", "apollo-server": "^2.16.0", diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts index 6845bb366e..784b801b86 100644 --- a/plugins/graphql/src/service/router.ts +++ b/plugins/graphql/src/service/router.ts @@ -23,7 +23,7 @@ import path from 'path'; import { GraphQLModule } from '@graphql-modules/core'; import { ApolloServer } from 'apollo-server-express'; import { createModule as createCatalogModule } from '@backstage/plugin-catalog-graphql'; -import { createModule as createBogusModule } from '@backstage/plugin-bogus-graphql'; +import { Config } from '@backstage/config'; const schemaPath = path.resolve( require.resolve('@backstage/plugin-graphql-backend/package.json'), @@ -32,6 +32,7 @@ const schemaPath = path.resolve( export interface RouterOptions { logger: Logger; + config: Config; } export async function createRouter( @@ -40,9 +41,8 @@ export async function createRouter( const typeDefs = await fs.promises.readFile(schemaPath, 'utf-8'); const catalogModule = await createCatalogModule(options); - const bogusModule = await createBogusModule(options); const { schema } = new GraphQLModule({ - imports: [catalogModule, bogusModule], + imports: [catalogModule], typeDefs, }); From b9c21c6fbaeedd5ea3de4295736b8c5e18d9e961 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Aug 2020 07:27:32 +0200 Subject: [PATCH 005/219] chore(gql): tidying up the graphql code --- plugins/catalog-graphql/.gitignore | 1 + plugins/catalog-graphql/codegen.yml | 11 +- plugins/catalog-graphql/package.json | 3 + .../catalog-graphql/src/generated-models.ts | 288 ------------------ plugins/catalog-graphql/src/graphql/module.ts | 61 ++-- plugins/catalog-graphql/src/schema.gql | 2 +- plugins/catalog-graphql/src/service/client.ts | 30 ++ 7 files changed, 68 insertions(+), 328 deletions(-) create mode 100644 plugins/catalog-graphql/.gitignore delete mode 100644 plugins/catalog-graphql/src/generated-models.ts create mode 100644 plugins/catalog-graphql/src/service/client.ts diff --git a/plugins/catalog-graphql/.gitignore b/plugins/catalog-graphql/.gitignore new file mode 100644 index 0000000000..5a5538170a --- /dev/null +++ b/plugins/catalog-graphql/.gitignore @@ -0,0 +1 @@ +src/graphql/types.ts diff --git a/plugins/catalog-graphql/codegen.yml b/plugins/catalog-graphql/codegen.yml index c9e0173256..ebdb34a938 100644 --- a/plugins/catalog-graphql/codegen.yml +++ b/plugins/catalog-graphql/codegen.yml @@ -1,15 +1,16 @@ overwrite: true -schema: ./src/schema.gql # You can use .js files as well +schema: ./src/schema.gql require: - - ts-node/register/transpile-only # required if you're using TS-Node + - ts-node/register/transpile-only generates: - ./src/generated-models.ts: + ./src/graphql/types.ts: plugins: - typescript - typescript-resolvers - hooks: afterOneFileWrite: - - prettier --write + - eslint --fix config: contextType: '@graphql-modules/core#ModuleContext' + useIndexSignature: true + defaultMapper: Partial<{T}> diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 8c478e767b..8a0c0474f6 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -17,11 +17,14 @@ "test": "backstage-cli test", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", + "postinstall": "graphql-codegen", "clean": "backstage-cli clean", "mock-data": "./scripts/mock-data.sh" }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.16", + "@backstage/catalog-model": "^0.1.1-alpha.16", + "@backstage/config": "^0.1.1-alpha.16", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", diff --git a/plugins/catalog-graphql/src/generated-models.ts b/plugins/catalog-graphql/src/generated-models.ts deleted file mode 100644 index f0f7f2f372..0000000000 --- a/plugins/catalog-graphql/src/generated-models.ts +++ /dev/null @@ -1,288 +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 { GraphQLResolveInfo } from 'graphql'; -import { ModuleContext } from '@graphql-modules/core'; - -export type Maybe = T | null; -export type Exact = { - [K in keyof T]: T[K]; -}; -export type RequireFields = { - [X in Exclude]?: T[X]; -} & - { [P in K]-?: NonNullable }; -/** All built-in and custom scalars, mapped to their actual values */ -export type Scalars = { - ID: string; - String: string; - Boolean: boolean; - Int: number; - Float: number; -}; - -export type EntityMetadataAnnotation = { - __typename?: 'EntityMetadataAnnotation'; - key: Scalars['String']; - value: Scalars['String']; -}; - -export type EntityMetadata = { - __typename?: 'EntityMetadata'; - name: Scalars['String']; - namespace?: Maybe; - annotation?: Maybe; - annotations: Array; -}; - -export type EntityMetadataAnnotationArgs = { - name?: Maybe; -}; - -export type CatalogEntity = { - __typename?: 'CatalogEntity'; - apiVersion?: Maybe; - kind: Scalars['String']; - metadata?: Maybe; -}; - -export type CatalogQuery = { - __typename?: 'CatalogQuery'; - list: Array; -}; - -export type Query = { - __typename?: 'Query'; - catalog: CatalogQuery; -}; - -export type ResolverTypeWrapper = Promise | T; - -export type LegacyStitchingResolver = { - fragment: string; - resolve: ResolverFn; -}; - -export type NewStitchingResolver = { - selectionSet: string; - resolve: ResolverFn; -}; -export type StitchingResolver = - | LegacyStitchingResolver - | NewStitchingResolver; -export type Resolver = - | ResolverFn - | StitchingResolver; - -export type ResolverFn = ( - parent: TParent, - args: TArgs, - context: TContext, - info: GraphQLResolveInfo, -) => Promise | TResult; - -export type SubscriptionSubscribeFn = ( - parent: TParent, - args: TArgs, - context: TContext, - info: GraphQLResolveInfo, -) => AsyncIterator | Promise>; - -export type SubscriptionResolveFn = ( - parent: TParent, - args: TArgs, - context: TContext, - info: GraphQLResolveInfo, -) => TResult | Promise; - -export interface SubscriptionSubscriberObject< - TResult, - TKey extends string, - TParent, - TContext, - TArgs -> { - subscribe: SubscriptionSubscribeFn< - { [key in TKey]: TResult }, - TParent, - TContext, - TArgs - >; - resolve?: SubscriptionResolveFn< - TResult, - { [key in TKey]: TResult }, - TContext, - TArgs - >; -} - -export interface SubscriptionResolverObject { - subscribe: SubscriptionSubscribeFn; - resolve: SubscriptionResolveFn; -} - -export type SubscriptionObject< - TResult, - TKey extends string, - TParent, - TContext, - TArgs -> = - | SubscriptionSubscriberObject - | SubscriptionResolverObject; - -export type SubscriptionResolver< - TResult, - TKey extends string, - TParent = {}, - TContext = {}, - TArgs = {} -> = - | (( - ...args: any[] - ) => SubscriptionObject) - | SubscriptionObject; - -export type TypeResolveFn = ( - parent: TParent, - context: TContext, - info: GraphQLResolveInfo, -) => Maybe | Promise>; - -export type IsTypeOfResolverFn = ( - obj: T, - info: GraphQLResolveInfo, -) => boolean | Promise; - -export type NextResolverFn = () => Promise; - -export type DirectiveResolverFn< - TResult = {}, - TParent = {}, - TContext = {}, - TArgs = {} -> = ( - next: NextResolverFn, - parent: TParent, - args: TArgs, - context: TContext, - info: GraphQLResolveInfo, -) => TResult | Promise; - -/** Mapping between all available schema types and the resolvers types */ -export type ResolversTypes = { - EntityMetadataAnnotation: ResolverTypeWrapper; - String: ResolverTypeWrapper; - EntityMetadata: ResolverTypeWrapper; - CatalogEntity: ResolverTypeWrapper; - CatalogQuery: ResolverTypeWrapper; - Query: ResolverTypeWrapper<{}>; - Boolean: ResolverTypeWrapper; -}; - -/** Mapping between all available schema types and the resolvers parents */ -export type ResolversParentTypes = { - EntityMetadataAnnotation: EntityMetadataAnnotation; - String: Scalars['String']; - EntityMetadata: EntityMetadata; - CatalogEntity: CatalogEntity; - CatalogQuery: CatalogQuery; - Query: {}; - Boolean: Scalars['Boolean']; -}; - -export type EntityMetadataAnnotationResolvers< - ContextType = ModuleContext, - ParentType extends ResolversParentTypes['EntityMetadataAnnotation'] = ResolversParentTypes['EntityMetadataAnnotation'] -> = { - key?: Resolver; - value?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type EntityMetadataResolvers< - ContextType = ModuleContext, - ParentType extends ResolversParentTypes['EntityMetadata'] = ResolversParentTypes['EntityMetadata'] -> = { - name?: Resolver; - namespace?: Resolver< - Maybe, - ParentType, - ContextType - >; - annotation?: Resolver< - Maybe, - ParentType, - ContextType, - RequireFields - >; - annotations?: Resolver< - Array, - ParentType, - ContextType - >; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type CatalogEntityResolvers< - ContextType = ModuleContext, - ParentType extends ResolversParentTypes['CatalogEntity'] = ResolversParentTypes['CatalogEntity'] -> = { - apiVersion?: Resolver< - Maybe, - ParentType, - ContextType - >; - kind?: Resolver; - metadata?: Resolver< - Maybe, - ParentType, - ContextType - >; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type CatalogQueryResolvers< - ContextType = ModuleContext, - ParentType extends ResolversParentTypes['CatalogQuery'] = ResolversParentTypes['CatalogQuery'] -> = { - list?: Resolver< - Array, - ParentType, - ContextType - >; - __isTypeOf?: IsTypeOfResolverFn; -}; - -export type QueryResolvers< - ContextType = ModuleContext, - ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query'] -> = { - catalog?: Resolver; -}; - -export type Resolvers = { - EntityMetadataAnnotation?: EntityMetadataAnnotationResolvers; - EntityMetadata?: EntityMetadataResolvers; - CatalogEntity?: CatalogEntityResolvers; - CatalogQuery?: CatalogQueryResolvers; - Query?: QueryResolvers; -}; - -/** - * @deprecated - * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. - */ -export type IResolvers = Resolvers; diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 7648b7d302..1fc13d7950 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -15,43 +15,28 @@ */ import { Logger } from 'winston'; -import fetch from 'node-fetch'; import fs from 'fs'; import path from 'path'; import { GraphQLModule } from '@graphql-modules/core'; -import { QueryResolvers } from './types'; +import { Resolvers, CatalogEntity, CatalogQuery } from './types'; +import { Entity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { CatalogClient } from '../service/client'; export interface ModuleOptions { logger: Logger; + config: Config; } -// function wrapLogger(logger: Logger) { -// return { -// log: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// debug: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// info: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// error: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// clientError: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// warn: (msg: string | Error, ...extra: any[]) => -// logger.info(String(msg), ...extra), -// }; -// } - -class CatalogClient { - async list() { - const res = await fetch('http://localhost:7000/catalog/entities'); - if (!res.ok) { - throw new Error(`NOPE, ${await res.text()}`); - } - - return res.json(); - } -} +const parseToCatalogEntities = (e: Entity): CatalogEntity => ({ + ...e, + metadata: { + ...e.metadata, + annotations: Object.entries( + e.metadata.annotations ?? {}, + ).map(([key, value]) => ({ key, value })), + }, +}); export async function createModule( options: ModuleOptions, @@ -61,16 +46,24 @@ export async function createModule( 'utf-8', ); - const catalogClient = new CatalogClient(); + const catalogClient = new CatalogClient( + options.config.getString('backend.baseUrl'), + ); - const resolvers = { + const resolvers: Resolvers = { Query: { - catalog: () => true, + catalog: () => ({} as CatalogQuery), }, CatalogQuery: { - list: () => catalogClient.list(), + list: async () => { + const list = await catalogClient.list(); + return list.map(parseToCatalogEntities); + }, + }, + EntityMetadata: { + annotation: (e, { name }) => + e.annotations?.find(a => a.key === name) ?? null, }, - CatalogEntity: () => ({}), }; const module = new GraphQLModule({ diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/src/schema.gql index ea92d4e04f..b28f796855 100644 --- a/plugins/catalog-graphql/src/schema.gql +++ b/plugins/catalog-graphql/src/schema.gql @@ -7,7 +7,7 @@ type EntityMetadata { name: String! namespace: String annotation(name: String): EntityMetadataAnnotation - annotations: [EntityMetadataAnnotation!]! + annotations: [EntityMetadataAnnotation!] } type CatalogEntity { diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts new file mode 100644 index 0000000000..091a8a8cc0 --- /dev/null +++ b/plugins/catalog-graphql/src/service/client.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import fetch from 'node-fetch'; + +export class CatalogClient { + constructor(private baseUrl: string) {} + async list(): Promise { + const res = await fetch(`${this.baseUrl}/catalog/entities`); + if (!res.ok) { + throw new Error(`NOPE, ${await res.text()}`); + } + + return await (res.json() as Promise); + } +} From 11de4c5f4bfa082384dab2951d9c8d7cbb7e02f7 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Aug 2020 07:27:51 +0200 Subject: [PATCH 006/219] gotcha(gql): if you just import the type the config library doesnt work :shrug: --- packages/backend/src/index.ts | 1 - packages/backend/src/plugins/graphql.ts | 8 +- yarn.lock | 981 +++++++++++++++++++++--- 3 files changed, 881 insertions(+), 109 deletions(-) diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 3fccfe9c59..d22e1088a7 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -88,7 +88,6 @@ async function main() { const configs = await loadConfig({ shouldReadSecrets: true }); 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')); diff --git a/packages/backend/src/plugins/graphql.ts b/packages/backend/src/plugins/graphql.ts index 0f9b167d19..d52e28f9cb 100644 --- a/packages/backend/src/plugins/graphql.ts +++ b/packages/backend/src/plugins/graphql.ts @@ -30,10 +30,14 @@ */ import { createRouter } from '@backstage/plugin-graphql-backend'; -import type { PluginEnvironment } from '../types'; +import { PluginEnvironment } from '../types'; -export default async function createPlugin({ logger }: PluginEnvironment) { +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment) { return await createRouter({ logger, + config, }); } diff --git a/yarn.lock b/yarn.lock index 9e463417b4..36a360c119 100644 --- a/yarn.lock +++ b/yarn.lock @@ -77,6 +77,28 @@ invariant "^2.2.4" semver "^5.5.0" +"@babel/core@^7.0.0": + version "7.11.1" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.11.1.tgz#2c55b604e73a40dc21b0e52650b11c65cf276643" + integrity sha512-XqF7F6FWQdKGGWAzGELL+aCO1p+lRY5Tj5/tbT3St1G8NaH70jhhDIKknIZaDans0OQBG5wRAldROLHSt44BgQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.11.0" + "@babel/helper-module-transforms" "^7.11.0" + "@babel/helpers" "^7.10.4" + "@babel/parser" "^7.11.1" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.11.0" + "@babel/types" "^7.11.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + "@babel/core@^7.1.0", "@babel/core@^7.4.4", "@babel/core@^7.4.5", "@babel/core@^7.7.5": version "7.9.6" resolved "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz#d9aa1f580abf3b2286ef40b6904d390904c63376" @@ -108,6 +130,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.11.0", "@babel/generator@^7.5.0": + version "7.11.0" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz#4b90c78d8c12825024568cbe83ee6c9af193585c" + integrity sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ== + dependencies: + "@babel/types" "^7.11.0" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/generator@^7.9.6": version "7.9.6" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" @@ -307,6 +338,19 @@ "@babel/types" "^7.10.5" lodash "^4.17.19" +"@babel/helper-module-transforms@^7.11.0": + version "7.11.0" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" + integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/template" "^7.10.4" + "@babel/types" "^7.11.0" + lodash "^4.17.19" + "@babel/helper-module-transforms@^7.9.0": version "7.9.0" resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" @@ -405,6 +449,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-skip-transparent-expression-wrappers@^7.11.0": + version "7.11.0" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz#eec162f112c2f58d3af0af125e3bb57665146729" + integrity sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q== + dependencies: + "@babel/types" "^7.11.0" + "@babel/helper-split-export-declaration@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1" @@ -412,6 +463,13 @@ dependencies: "@babel/types" "^7.10.4" +"@babel/helper-split-export-declaration@^7.11.0": + version "7.11.0" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== + dependencies: + "@babel/types" "^7.11.0" + "@babel/helper-split-export-declaration@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" @@ -439,6 +497,15 @@ "@babel/traverse" "^7.10.4" "@babel/types" "^7.10.4" +"@babel/helpers@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044" + integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA== + dependencies: + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helpers@^7.9.6": version "7.9.6" resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz#092c774743471d0bb6c7de3ad465ab3d3486d580" @@ -466,16 +533,21 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/parser@7.10.5", "@babel/parser@^7.10.4", "@babel/parser@^7.10.5": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" + integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== + +"@babel/parser@^7.0.0", "@babel/parser@^7.11.0", "@babel/parser@^7.11.1": + version "7.11.1" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.11.1.tgz#d91a387990b21e5d20047b336bb19b0553f02ff5" + integrity sha512-u9QMIRdKVF7hfEkb3nu2LgZDIzCQPv+yHD9Eg6ruoJLjkrQ9fFz4IBSlF/9XwoNri9+2F1IY+dYuOfZrXq8t3w== + "@babel/parser@^7.1.0", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.6": version "7.9.6" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" integrity sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q== -"@babel/parser@^7.10.4", "@babel/parser@^7.10.5": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" - integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== - "@babel/plugin-proposal-async-generator-functions@^7.10.4": version "7.10.5" resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558" @@ -485,7 +557,7 @@ "@babel/helper-remap-async-to-generator" "^7.10.4" "@babel/plugin-syntax-async-generators" "^7.8.0" -"@babel/plugin-proposal-class-properties@^7.10.4": +"@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== @@ -533,6 +605,15 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-numeric-separator" "^7.10.4" +"@babel/plugin-proposal-object-rest-spread@^7.0.0": + version "7.11.0" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz#bd81f95a1f746760ea43b6c2d3d62b11790ad0af" + integrity sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0" @@ -604,7 +685,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.10.4": +"@babel/plugin-syntax-class-properties@^7.0.0", "@babel/plugin-syntax-class-properties@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c" integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA== @@ -625,6 +706,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.10.4.tgz#53351dd7ae01995e567d04ce42af1a6e0ba846a6" + integrity sha512-yxQsX1dJixF4qEEdzVbst3SZQ58Nrooz8NV9Z9GL4byTE25BvJgl5lf0RECUf0fh28rZBb/RYTWn/eeKwCMrZQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-flow@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.8.3.tgz#f2c883bd61a6316f2c89380ae5122f923ba4527f" @@ -639,7 +727,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.10.4": +"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz#39abaae3cbf710c4373d8429484e6ba21340166c" integrity sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g== @@ -674,7 +762,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": +"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== @@ -702,7 +790,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-arrow-functions@^7.10.4": +"@babel/plugin-transform-arrow-functions@^7.0.0", "@babel/plugin-transform-arrow-functions@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd" integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA== @@ -718,13 +806,20 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/helper-remap-async-to-generator" "^7.10.4" -"@babel/plugin-transform-block-scoped-functions@^7.10.4": +"@babel/plugin-transform-block-scoped-functions@^7.0.0", "@babel/plugin-transform-block-scoped-functions@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8" integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA== dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-block-scoping@^7.0.0": + version "7.11.1" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz#5b7efe98852bef8d652c0b28144cd93a9e4b5215" + integrity sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-block-scoping@^7.10.4": version "7.10.5" resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz#b81b8aafefbfe68f0f65f7ef397b9ece68a6037d" @@ -732,7 +827,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-classes@^7.10.4": +"@babel/plugin-transform-classes@^7.0.0", "@babel/plugin-transform-classes@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7" integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA== @@ -746,14 +841,14 @@ "@babel/helper-split-export-declaration" "^7.10.4" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.10.4": +"@babel/plugin-transform-computed-properties@^7.0.0", "@babel/plugin-transform-computed-properties@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb" integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-destructuring@^7.10.4": +"@babel/plugin-transform-destructuring@^7.0.0", "@babel/plugin-transform-destructuring@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5" integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA== @@ -791,6 +886,14 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-flow-strip-types@^7.0.0": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.10.4.tgz#c497957f09e86e3df7296271e9eb642876bf7788" + integrity sha512-XTadyuqNst88UWBTdLjM+wEY7BFnY2sYtPyAidfC7M/QaZnSuIZpMvLxqGT7phAcnGyWh/XQFLKcGf04CnvxSQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-flow" "^7.10.4" + "@babel/plugin-transform-flow-strip-types@^7.9.0": version "7.9.0" resolved "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz#8a3538aa40434e000b8f44a3c5c9ac7229bd2392" @@ -799,14 +902,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-flow" "^7.8.3" -"@babel/plugin-transform-for-of@^7.10.4": +"@babel/plugin-transform-for-of@^7.0.0", "@babel/plugin-transform-for-of@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9" integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-function-name@^7.10.4": +"@babel/plugin-transform-function-name@^7.0.0", "@babel/plugin-transform-function-name@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7" integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg== @@ -814,14 +917,14 @@ "@babel/helper-function-name" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-literals@^7.10.4": +"@babel/plugin-transform-literals@^7.0.0", "@babel/plugin-transform-literals@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c" integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-member-expression-literals@^7.10.4": +"@babel/plugin-transform-member-expression-literals@^7.0.0", "@babel/plugin-transform-member-expression-literals@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7" integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw== @@ -837,7 +940,7 @@ "@babel/helper-plugin-utils" "^7.10.4" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.10.4": +"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0" integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w== @@ -889,7 +992,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-object-super@^7.10.4": +"@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894" integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ== @@ -897,7 +1000,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/helper-replace-supers" "^7.10.4" -"@babel/plugin-transform-parameters@^7.10.4": +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.10.4": version "7.10.5" resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz#59d339d58d0b1950435f4043e74e2510005e2c4a" integrity sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw== @@ -905,7 +1008,7 @@ "@babel/helper-get-function-arity" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-property-literals@^7.10.4": +"@babel/plugin-transform-property-literals@^7.0.0", "@babel/plugin-transform-property-literals@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0" integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g== @@ -919,7 +1022,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-display-name@^7.10.4": +"@babel/plugin-transform-react-display-name@^7.0.0", "@babel/plugin-transform-react-display-name@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz#b5795f4e3e3140419c3611b7a2a3832b9aef328d" integrity sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw== @@ -951,7 +1054,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-jsx" "^7.10.4" -"@babel/plugin-transform-react-jsx@^7.10.4": +"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz#673c9f913948764a4421683b2bef2936968fddf2" integrity sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A== @@ -983,13 +1086,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-shorthand-properties@^7.10.4": +"@babel/plugin-transform-shorthand-properties@^7.0.0", "@babel/plugin-transform-shorthand-properties@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q== dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-spread@^7.0.0": + version "7.11.0" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz#fa84d300f5e4f57752fe41a6d1b3c554f13f17cc" + integrity sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0" + "@babel/plugin-transform-spread@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz#4e2c85ea0d6abaee1b24dcfbbae426fe8d674cff" @@ -1005,7 +1116,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/helper-regex" "^7.10.4" -"@babel/plugin-transform-template-literals@^7.10.4": +"@babel/plugin-transform-template-literals@^7.0.0", "@babel/plugin-transform-template-literals@^7.10.4": version "7.10.5" resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz#78bc5d626a6642db3312d9d0f001f5e7639fde8c" integrity sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw== @@ -1186,6 +1297,36 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" +"@babel/traverse@7.10.5", "@babel/traverse@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564" + integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/types" "^7.10.5" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/traverse@^7.0.0", "@babel/traverse@^7.11.0": + version "7.11.0" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24" + integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.11.0" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/parser" "^7.11.0" + "@babel/types" "^7.11.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + "@babel/traverse@^7.1.0", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.6": version "7.9.6" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442" @@ -1201,20 +1342,14 @@ globals "^11.1.0" lodash "^4.17.13" -"@babel/traverse@^7.10.4": +"@babel/types@7.10.5", "@babel/types@^7.10.4", "@babel/types@^7.10.5": version "7.10.5" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564" - integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ== + resolved "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15" + integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q== dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.10.5" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.10.4" - "@babel/parser" "^7.10.5" - "@babel/types" "^7.10.5" - debug "^4.1.0" - globals "^11.1.0" + "@babel/helper-validator-identifier" "^7.10.4" lodash "^4.17.19" + to-fast-properties "^2.0.0" "@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6": version "7.9.6" @@ -1225,10 +1360,10 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@babel/types@^7.10.4", "@babel/types@^7.10.5": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15" - integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q== +"@babel/types@^7.11.0": + version "7.11.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" + integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== dependencies: "@babel/helper-validator-identifier" "^7.10.4" lodash "^4.17.19" @@ -1479,6 +1614,119 @@ unique-filename "^1.1.1" which "^1.3.1" +"@graphql-codegen/cli@^1.17.7": + version "1.17.7" + resolved "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-1.17.7.tgz#4ba45106403a3aca36585bf04f0169fda38ac9df" + integrity sha512-T9b4r7WkA+VfW8nsAjOWFoFbtpfFAAjPQM6q/8aPg6Ny3rwwefH8UDVa3P3QSfdIYl2qKnBk1FG2mNjBCUYKsg== + dependencies: + "@graphql-codegen/core" "1.17.7" + "@graphql-codegen/plugin-helpers" "1.17.7" + "@graphql-tools/apollo-engine-loader" "^6.0.0" + "@graphql-tools/code-file-loader" "^6.0.0" + "@graphql-tools/git-loader" "^6.0.0" + "@graphql-tools/github-loader" "^6.0.0" + "@graphql-tools/graphql-file-loader" "^6.0.0" + "@graphql-tools/json-file-loader" "^6.0.0" + "@graphql-tools/load" "^6.0.0" + "@graphql-tools/prisma-loader" "^6.0.0" + "@graphql-tools/url-loader" "^6.0.0" + "@graphql-tools/utils" "^6.0.0" + ansi-escapes "4.3.1" + camel-case "4.1.1" + chalk "4.1.0" + chokidar "3.4.1" + common-tags "1.8.0" + constant-case "3.0.3" + cosmiconfig "7.0.0" + debounce "1.2.0" + dependency-graph "0.9.0" + detect-indent "6.0.0" + glob "7.1.6" + graphql-config "^3.0.2" + indent-string "4.0.0" + inquirer "7.3.3" + is-glob "4.0.1" + json-to-pretty-yaml "1.2.2" + listr "0.14.3" + listr-update-renderer "0.5.0" + log-symbols "4.0.0" + lower-case "2.0.1" + minimatch "3.0.4" + mkdirp "1.0.4" + pascal-case "3.1.1" + request "2.88.2" + string-env-interpolation "1.0.1" + ts-log "2.1.4" + tslib "^2.0.0" + upper-case "2.0.1" + valid-url "1.0.9" + wrap-ansi "7.0.0" + yargs "15.4.1" + +"@graphql-codegen/core@1.17.7": + version "1.17.7" + resolved "https://registry.npmjs.org/@graphql-codegen/core/-/core-1.17.7.tgz#9f2f5798ec1c551827aa4ce6417d58cf48cd2a92" + integrity sha512-KfVoNtGCHvj/oN22Yl7ljPC9LkxvAAGDVPilYjJa3GjcT6YvF5k+ijyZ3GMjU8belN3g04Ls/WpKUsWA6HpkiQ== + dependencies: + "@graphql-codegen/plugin-helpers" "1.17.7" + "@graphql-tools/merge" "^6.0.0" + "@graphql-tools/utils" "^6.0.0" + tslib "~2.0.0" + +"@graphql-codegen/plugin-helpers@1.17.7": + version "1.17.7" + resolved "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.17.7.tgz#5903105fda9470aafefe9da29e3a6fb3a52b8376" + integrity sha512-LsXS0s/ZOACZXa3W29ekcaQLzP8TsYzow6nIjW6rtkWX5T0EDooBQbDn1cdLdlpenqbUU+vtONwR6Qqc6hrq2Q== + dependencies: + "@graphql-tools/utils" "^6.0.0" + camel-case "4.1.1" + common-tags "1.8.0" + constant-case "3.0.3" + import-from "3.0.0" + lodash "~4.17.15" + lower-case "2.0.1" + param-case "3.0.3" + pascal-case "3.1.1" + tslib "~2.0.0" + upper-case "2.0.1" + +"@graphql-codegen/typescript-resolvers@^1.17.7": + version "1.17.7" + resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-1.17.7.tgz#fe912db2abeab36daf747108607d09bbc97b82fa" + integrity sha512-TFi7P8xSymFLj46HA8o/eLHPsY2X4z/maID+2rSaIYi+7IXVn2aHgvhFpvmmjIuCEdRvVH5Gdss7i7hwbaxW6w== + dependencies: + "@graphql-codegen/plugin-helpers" "1.17.7" + "@graphql-codegen/typescript" "1.17.7" + "@graphql-codegen/visitor-plugin-common" "1.17.7" + "@graphql-tools/utils" "^6.0.0" + auto-bind "~4.0.0" + tslib "~2.0.0" + +"@graphql-codegen/typescript@1.17.7", "@graphql-codegen/typescript@^1.17.7": + version "1.17.7" + resolved "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-1.17.7.tgz#35d92da76d4e07b612ab1ac0bd9726fbaac1fb0d" + integrity sha512-itjssTRBIvZgoNwzz/mhDkaeZvoMqHaeN+PDqev+6gY+RDDGcr9fsEm/SLm9nOpBWZCAHFVc9u7wHlSPhbYGtw== + dependencies: + "@graphql-codegen/plugin-helpers" "1.17.7" + "@graphql-codegen/visitor-plugin-common" "1.17.7" + auto-bind "~4.0.0" + tslib "~2.0.0" + +"@graphql-codegen/visitor-plugin-common@1.17.7": + version "1.17.7" + resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.17.7.tgz#3158ca4fc7d45a0f5a6ad0706061015eae481d9e" + integrity sha512-z5WvYqgCgPAAuMJMOE0e0nEicdaQRm59/vP+yYihKQmwrASzPlqa1BUiGDnfrPcCooB9fEUHB+cQb3gt9GRfEg== + dependencies: + "@graphql-codegen/plugin-helpers" "1.17.7" + "@graphql-tools/relay-operation-optimizer" "6.0.15" + array.prototype.flatmap "1.2.3" + auto-bind "~4.0.0" + dependency-graph "0.9.0" + graphql-tag "2.11.0" + parse-filepath "1.0.2" + pascal-case "3.1.1" + tslib "~2.0.0" + "@graphql-modules/core@^0.7.17": version "0.7.17" resolved "https://registry.npmjs.org/@graphql-modules/core/-/core-0.7.17.tgz#ae9bbf44894509664bc0b6fcdd9b6cf3d99bcd00" @@ -1520,6 +1768,25 @@ graphql-tools "5.0.0" tslib "1.11.1" +"@graphql-tools/apollo-engine-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-6.0.15.tgz#05ceca3ba024b5fbd4ac4366cf0cfc88e0fd7ef4" + integrity sha512-9SwaZZPr03dZh3rqmpw6GG2S+SzSuBCfYgx/cY37TPPB+wTJjBzZSzRGrC8hsUlbGLF7Qzq7rFYxfl0zGYpxXg== + dependencies: + "@graphql-tools/utils" "6.0.15" + cross-fetch "3.0.5" + tslib "~2.0.0" + +"@graphql-tools/code-file-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-6.0.15.tgz#d4c04b6493ac658da13bb115e7ed6821e2c687f1" + integrity sha512-Z4RLnJzlHWHbfe/yRFLqWuqxd6//dSxj/87FiOwN2GR+o/7I0N7ppZIAsLleVDM0LghDO+Nnt1cacNAEmTd0Fw== + dependencies: + "@graphql-tools/graphql-tag-pluck" "6.0.15" + "@graphql-tools/utils" "6.0.15" + fs-extra "9.0.1" + tslib "~2.0.0" + "@graphql-tools/delegate@6.0.15": version "6.0.15" resolved "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.15.tgz#9e060bfc31fe7735bd5b2b401e98dea3fa5d3b25" @@ -1530,6 +1797,23 @@ "@graphql-tools/utils" "6.0.15" tslib "~2.0.0" +"@graphql-tools/git-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-6.0.15.tgz#b9c70e332943372f50c7ef4e1161a65d5bdac08e" + integrity sha512-Tt33twi6oe++Hk+CeEl8A6DxETunwLG9EOkdHglxIKFn5k6yt8sd2hninwGjj8uyceC6CpeChN6jkF6Hg3N7fA== + dependencies: + "@graphql-tools/graphql-tag-pluck" "6.0.15" + "@graphql-tools/utils" "6.0.15" + +"@graphql-tools/github-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-6.0.15.tgz#2faccd5d20de87fba4762aa0e33659edd924da54" + integrity sha512-JYZp+u84JHJUB84dAlAHVgCxWtots6qtqMeENHPgNnpkci0cYKHAw+Mna3rZCEXR+9m4QKkM6K35U9cHODc6BA== + dependencies: + "@graphql-tools/graphql-tag-pluck" "6.0.15" + "@graphql-tools/utils" "6.0.15" + cross-fetch "3.0.5" + "@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" @@ -1540,6 +1824,18 @@ fs-extra "9.0.1" tslib "~2.0.0" +"@graphql-tools/graphql-tag-pluck@6.0.15": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-6.0.15.tgz#0e519202bc21faf068a22528b2e10d196e181aa6" + integrity sha512-d27PwoTIz3mQumnFtWjn60dulpePvWG6D8itPTGgl75H7LNpIdKPYlLTTcPcFsfWiFb4yIgg6/oarU8pJfRAsw== + dependencies: + "@babel/parser" "7.10.5" + "@babel/traverse" "7.10.5" + "@babel/types" "7.10.5" + "@graphql-tools/utils" "6.0.15" + optionalDependencies: + vue-template-compiler "^2.6.11" + "@graphql-tools/import@6.0.15": version "6.0.15" resolved "https://registry.npmjs.org/@graphql-tools/import/-/import-6.0.15.tgz#d6a9d3b6199a1e07d80d8021635d317b2c190c6f" @@ -1581,6 +1877,25 @@ "@graphql-tools/utils" "6.0.15" tslib "~2.0.0" +"@graphql-tools/prisma-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/prisma-loader/-/prisma-loader-6.0.15.tgz#9d67046cc577f0f9028d56a0ff29ac454ded5165" + integrity sha512-jitPrrJgR5RV0F557dLubHQ5XeID9Kc9G/Owp/ux2CPRJRoagm/4rudlA4LbE87vzO2EGKUJiYsFRHlTZuCqAg== + dependencies: + "@graphql-tools/url-loader" "6.0.15" + "@graphql-tools/utils" "6.0.15" + fs-extra "9.0.1" + prisma-yml "1.34.10" + tslib "~2.0.0" + +"@graphql-tools/relay-operation-optimizer@6.0.15": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.0.15.tgz#f991499c54945cb8fa2396bb5292252fbda0a773" + integrity sha512-Y4h2kclKh5HvyvmoxeZiDhqdhMKfLKamOYx6UVpFsbeKb6Tt9RCNPVhpa+YToXxUXl0PvjhxZWeQ4lZY0GE0ug== + dependencies: + "@graphql-tools/utils" "6.0.15" + relay-compiler "10.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" @@ -1589,7 +1904,7 @@ "@graphql-tools/utils" "6.0.15" tslib "~2.0.0" -"@graphql-tools/url-loader@^6.0.0": +"@graphql-tools/url-loader@6.0.15", "@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== @@ -5284,6 +5599,16 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== +ajv@5: + version "5.5.2" + resolved "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.5.5, ajv@^6.7.0: version "6.12.2" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" @@ -5311,18 +5636,18 @@ ansi-colors@^3.0.0, ansi-colors@^3.2.1: resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== -ansi-escapes@^3.0.0, ansi-escapes@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - -ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: +ansi-escapes@4.3.1, ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: version "4.3.1" resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== dependencies: type-fest "^0.11.0" +ansi-escapes@^3.0.0, ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + ansi-html@0.0.7: version "0.0.7" resolved "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" @@ -5758,7 +6083,7 @@ array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -array.prototype.flatmap@^1.2.1: +array.prototype.flatmap@1.2.3, array.prototype.flatmap@^1.2.1: version "1.2.3" resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz#1c13f84a178566042dd63de4414440db9222e443" integrity sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg== @@ -5895,6 +6220,11 @@ atob@^2.1.2: resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +auto-bind@~4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb" + integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ== + autoprefixer@^9.7.2: version "9.7.4" resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378" @@ -6140,6 +6470,11 @@ babel-plugin-syntax-jsx@^6.18.0: resolved "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= +babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf" + integrity sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ== + babel-plugin-transform-inline-consecutive-adds@^0.4.3: version "0.4.3" resolved "https://registry.npmjs.org/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz#323d47a3ea63a83a7ac3c811ae8e6941faf2b0d1" @@ -6215,6 +6550,39 @@ babel-preset-current-node-syntax@^0.1.2: "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" +babel-preset-fbjs@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.3.0.tgz#a6024764ea86c8e06a22d794ca8b69534d263541" + integrity sha512-7QTLTCd2gwB2qGoi5epSULMHugSVgpcVt5YAeiFO9ABLrutDQzKfGwzxgZHLpugq8qMdg/DhRZDZ5CLKxBkEbw== + dependencies: + "@babel/plugin-proposal-class-properties" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0" + "@babel/plugin-syntax-class-properties" "^7.0.0" + "@babel/plugin-syntax-flow" "^7.0.0" + "@babel/plugin-syntax-jsx" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-block-scoped-functions" "^7.0.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.0.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.0.0" + "@babel/plugin-transform-flow-strip-types" "^7.0.0" + "@babel/plugin-transform-for-of" "^7.0.0" + "@babel/plugin-transform-function-name" "^7.0.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-member-expression-literals" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-object-super" "^7.0.0" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-property-literals" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-template-literals" "^7.0.0" + babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" + babel-preset-jest@^26.0.0: version "26.0.0" resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.0.0.tgz#1eac82f513ad36c4db2e9263d7c485c825b1faa6" @@ -6614,6 +6982,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" @@ -6925,6 +7298,14 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4. escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@4.1.0, chalk@^4.0.0, chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -6944,14 +7325,6 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - char-regex@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -6987,6 +7360,21 @@ check-types@^11.1.1: resolved "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f" integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ== +chokidar@3.4.1, chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.3.1: + version "3.4.1" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" + integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.4.0" + optionalDependencies: + fsevents "~2.1.2" + chokidar@^2.0.4, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -7006,21 +7394,6 @@ chokidar@^2.0.4, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.3.1: - version "3.4.1" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" - integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.4.0" - optionalDependencies: - fsevents "~2.1.2" - chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.2: version "1.1.4" resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -7147,6 +7520,11 @@ cli-width@^2.0.0: resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + clipboard@^2.0.0: version "2.0.6" resolved "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376" @@ -7525,6 +7903,15 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= +constant-case@3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/constant-case/-/constant-case-3.0.3.tgz#ac910a99caf3926ac5112f352e3af599d8c5fc0a" + integrity sha512-FXtsSnnrFYpzDmvwDGQW+l8XK3GV1coLyBN0eBz16ZUzGaZcT2ANVCJmLeuw2GQgxKHQIe9e0w2dzkSfaRlUmA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + upper-case "^2.0.1" + constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -7712,7 +8099,7 @@ core-js-pure@^3.0.0, core-js-pure@^3.0.1: resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a" integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== -core-js@^2.4.0, core-js@^2.5.7, core-js@^2.6.5: +core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.7, core-js@^2.6.5: version "2.6.11" resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== @@ -7759,6 +8146,17 @@ cosmiconfig@6.0.0, cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" +cosmiconfig@7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + 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" @@ -7815,6 +8213,14 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" +cross-fetch@2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.2.tgz#a47ff4f7fc712daba8f6a695a11c948440d45723" + integrity sha1-pH/09/xxLauo9qaVoRyUhEDUVyM= + dependencies: + node-fetch "2.1.2" + whatwg-fetch "2.0.4" + cross-fetch@3.0.5: version "3.0.5" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.5.tgz#2739d2981892e7ab488a7ad03b92df2816e03f4c" @@ -8291,7 +8697,12 @@ dateformat@^3.0.0: resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -debounce@^1.2.0: +de-indent@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" + integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= + +debounce@1.2.0, debounce@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== @@ -8512,6 +8923,11 @@ depd@~1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +dependency-graph@0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.9.0.tgz#11aed7e203bc8b00f48356d92db27b265c445318" + integrity sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w== + deprecated-decorator@^0.1.6: version "0.1.6" resolved "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" @@ -8540,6 +8956,11 @@ detect-file@^1.0.0: resolved "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= +detect-indent@6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" + integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== + detect-indent@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" @@ -8877,6 +9298,11 @@ dotenv-webpack@^1.7.0: dependencies: dotenv-defaults "^1.0.2" +dotenv@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" + integrity sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0= + dotenv@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" @@ -8915,6 +9341,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -9774,6 +10207,11 @@ fast-deep-equal@2.0.1, fast-deep-equal@^2.0.1: resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= + fast-deep-equal@^3.0.0, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -9875,6 +10313,25 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" + integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA== + dependencies: + core-js "^2.4.1" + fbjs-css-vars "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -10889,6 +11346,13 @@ graphql-language-service@^3.0.0: graphql-language-service-interface "^2.4.0" graphql-language-service-types "^1.6.0" +graphql-request@^1.5.0: + version "1.8.2" + resolved "https://registry.npmjs.org/graphql-request/-/graphql-request-1.8.2.tgz#398d10ae15c585676741bde3fc01d5ca948f8fbe" + integrity sha512-dDX2M+VMsxXFCmUX0Vo0TopIZIX4ggzOtiCsThgtrKR4niiaagsGTDIHj3fsOMFETpa064vzovI+4YV4QnMbcg== + dependencies: + cross-fetch "2.2.2" + graphql-subscriptions@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz#5f2fa4233eda44cf7570526adfcf3c16937aef11" @@ -10896,6 +11360,11 @@ graphql-subscriptions@^1.0.0: dependencies: iterall "^1.2.1" +graphql-tag@2.11.0: + version "2.11.0" + resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz#1deb53a01c46a7eb401d6cb59dec86fa1cccbffd" + integrity sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA== + graphql-tag@^2.9.2: version "2.10.4" resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.4.tgz#2f301a98219be8b178a6453bb7e33b79b66d8f83" @@ -11112,7 +11581,7 @@ hastscript@^5.0.0: property-information "^5.0.0" space-separated-tokens "^1.0.0" -he@^1.2.0: +he@^1.1.0, he@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -11460,7 +11929,7 @@ https-browserify@^1.0.0: resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -https-proxy-agent@^2.2.3: +https-proxy-agent@^2.2.1, https-proxy-agent@^2.2.3: version "2.2.4" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -11574,6 +12043,11 @@ immutable@>=3.8.2, immutable@^3.8.2: resolved "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM= +immutable@~3.7.6: + version "3.7.6" + resolved "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" + integrity sha1-E7TTyxK++hVIKib+Gy665kAHHks= + import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -11596,7 +12070,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.0.0, import-fresh@^3.1.0: +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== @@ -11644,6 +12118,11 @@ imurmurhash@^0.1.4: resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indent-string@4.0.0, indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + indent-string@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -11656,11 +12135,6 @@ indent-string@^3.0.0: resolved "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -11759,6 +12233,25 @@ inquirer@7.0.4: strip-ansi "^5.1.0" through "^2.3.6" +inquirer@7.3.3: + version "7.3.3" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + inquirer@^6.2.0: version "6.5.2" resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" @@ -12262,7 +12755,7 @@ is-ssh@^1.3.0: dependencies: protocols "^1.1.0" -is-stream@^1.1.0: +is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -12382,6 +12875,14 @@ isobject@^4.0.0: resolved "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== +isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -12897,6 +13398,14 @@ js-tokens@^3.0.2: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= +js-yaml@^3.10.0: + version "3.14.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@^3.13.1, js-yaml@^3.8.3: version "3.13.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -13010,6 +13519,11 @@ json-schema-merge-allof@^0.6.0: json-schema-compare "^0.2.2" lodash "^4.17.4" +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -13030,11 +13544,26 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= + dependencies: + jsonify "~0.0.0" + json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json-to-pretty-yaml@1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz#f4cd0bd0a5e8fe1df25aaf5ba118b099fd992d5b" + integrity sha1-9M0L0KXo/h3yWq9boRiwmf2ZLVs= + dependencies: + remedial "^1.0.7" + remove-trailing-spaces "^1.0.6" + json3@^3.3.2: version "3.3.3" resolved "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" @@ -13092,6 +13621,22 @@ jsonpointer@^4.0.1: resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= +jsonwebtoken@^8.1.0: + version "8.5.1" + resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -13179,6 +13724,23 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: array-includes "^3.0.3" object.assign "^4.1.0" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + jwt-decode@2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" @@ -13416,7 +13978,7 @@ listr-silent-renderer@^1.1.1: resolved "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4= -listr-update-renderer@^0.5.0: +listr-update-renderer@0.5.0, listr-update-renderer@^0.5.0: version "0.5.0" resolved "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2" integrity sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA== @@ -13611,17 +14173,47 @@ lodash.get@^4.4.2: resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + lodash.memoize@4.x, lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.once@^4.1.1: +lodash.once@^4.0.0, lodash.once@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= @@ -13671,7 +14263,7 @@ lodash@4.17.15: resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@^4.2.1: +lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@^4.2.1, lodash@~4.17.15: version "4.17.19" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== @@ -13683,6 +14275,13 @@ log-symbols@3.0.0, log-symbols@^3.0.0: dependencies: chalk "^2.4.2" +log-symbols@4.0.0, log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + dependencies: + chalk "^4.0.0" + log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -13690,13 +14289,6 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" -log-symbols@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== - dependencies: - chalk "^4.0.0" - log-update@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" @@ -13752,7 +14344,7 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" -lower-case@^2.0.1: +lower-case@2.0.1, lower-case@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== @@ -14346,7 +14938,7 @@ mkdirp-promise@^5.0.1: dependencies: mkdirp "*" -mkdirp@*, mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4: +mkdirp@*, mkdirp@1.0.4, mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -14592,11 +15184,24 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" +node-fetch@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" + integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= + 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== +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + node-forge@0.9.0: version "0.9.0" resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" @@ -14942,6 +15547,11 @@ nth-check@^1.0.2, nth-check@~1.0.1: dependencies: boolbase "~1.0.0" +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" @@ -15432,7 +16042,7 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -param-case@^3.0.3: +param-case@3.0.3, param-case@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" integrity sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA== @@ -15471,7 +16081,7 @@ parse-entities@^1.1.0, parse-entities@^1.1.2: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" -parse-filepath@^1.0.1: +parse-filepath@1.0.2, parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= @@ -15553,7 +16163,7 @@ parseurl@^1.3.2, parseurl@~1.3.2, parseurl@~1.3.3: resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -pascal-case@^3.1.1: +pascal-case@3.1.1, pascal-case@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== @@ -16527,6 +17137,35 @@ pretty-hrtime@^1.0.3: resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= +prisma-json-schema@0.1.3: + version "0.1.3" + resolved "https://registry.npmjs.org/prisma-json-schema/-/prisma-json-schema-0.1.3.tgz#6c302db8f464f8b92e8694d3f7dd3f41ac9afcbe" + integrity sha512-XZrf2080oR81mY8/OC8al68HiwBm0nXlFE727JIia0ZbNqwuV4MyRYk6E0+OIa6/9KEYxZrcAmoBs3EW1cCvnA== + +prisma-yml@1.34.10: + version "1.34.10" + resolved "https://registry.npmjs.org/prisma-yml/-/prisma-yml-1.34.10.tgz#0ef1ad3a125f54f200289cba56bdd9597f17f410" + integrity sha512-N9on+Cf/XQKFGUULk/681tnpfqiZ19UBTurFMm+/9rnml37mteDaFr2k8yz+K8Gt2xpEJ7kBu7ikG5PrXI1uoA== + dependencies: + ajv "5" + bluebird "^3.5.1" + chalk "^2.3.0" + debug "^3.1.0" + dotenv "^4.0.0" + fs-extra "^7.0.0" + graphql-request "^1.5.0" + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.1" + isomorphic-fetch "^2.2.1" + js-yaml "^3.10.0" + json-stable-stringify "^1.0.1" + jsonwebtoken "^8.1.0" + lodash "^4.17.4" + prisma-json-schema "0.1.3" + replaceall "^0.1.6" + scuid "^1.0.2" + yaml-ast-parser "^0.0.40" + prismjs@^1.8.4: version "1.19.0" resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.19.0.tgz#713afbd45c3baca4b321569f2df39e17e729d4dc" @@ -16604,6 +17243,13 @@ promise.series@^0.2.0: resolved "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" integrity sha1-LMfr6Vn8OmYZwEq029yeRS2GS70= +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + promise@^8.0.3: version "8.1.0" resolved "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" @@ -17666,6 +18312,36 @@ relateurl@^0.2.7: resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= +relay-compiler@10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/relay-compiler/-/relay-compiler-10.0.0.tgz#04d50d8ec53e3f683bc379b756cf0542a76105af" + integrity sha512-EVBMcMCiP+waOPR2930cNCCsac1sNhfQayzS+bOEMz2Lls5Bx7grhaadkBZLTEdCHQ1kf7lrsmcMDqj9mxABFw== + dependencies: + "@babel/core" "^7.0.0" + "@babel/generator" "^7.5.0" + "@babel/parser" "^7.0.0" + "@babel/runtime" "^7.0.0" + "@babel/traverse" "^7.0.0" + "@babel/types" "^7.0.0" + babel-preset-fbjs "^3.3.0" + chalk "^4.0.0" + fb-watchman "^2.0.0" + fbjs "^1.0.0" + glob "^7.1.1" + immutable "~3.7.6" + nullthrows "^1.1.1" + relay-runtime "10.0.0" + signedsource "^1.0.0" + yargs "^15.3.1" + +relay-runtime@10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/relay-runtime/-/relay-runtime-10.0.0.tgz#cfceb0f8453b39a385d63093f2dbf1702ddc02b3" + integrity sha512-QEpFwEjvGgWgQ0MPJyrZKggaCoGMKwxPQx7NwYl4FcMmxZcicc8wk6vI1iTxl0tsPKgW/YG8FgueQR+X7ZtZqw== + dependencies: + "@babel/runtime" "^7.0.0" + fbjs "^1.0.0" + remark-parse@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95" @@ -17687,11 +18363,21 @@ remark-parse@^5.0.0: vfile-location "^2.0.0" xtend "^4.0.1" +remedial@^1.0.7: + version "1.0.8" + resolved "https://registry.npmjs.org/remedial/-/remedial-1.0.8.tgz#a5e4fd52a0e4956adbaf62da63a5a46a78c578a0" + integrity sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg== + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= +remove-trailing-spaces@^1.0.6: + version "1.0.7" + resolved "https://registry.npmjs.org/remove-trailing-spaces/-/remove-trailing-spaces-1.0.7.tgz#491f04e11d98880714d12429b0d0938cbe030ae6" + integrity sha512-wjM17CJ2kk0SgoGyJ7ZMzRRCuTq+V8YhMwpZ5XEWX0uaked2OUq6utvHXGNBQrfkUzUUABFMyxlKn+85hMv4dg== + renderkid@^2.0.1: version "2.0.3" resolved "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149" @@ -17734,6 +18420,11 @@ replace-in-file@^6.0.0: glob "^7.1.6" yargs "^15.3.1" +replaceall@^0.1.6: + version "0.1.6" + resolved "https://registry.npmjs.org/replaceall/-/replaceall-0.1.6.tgz#81d81ac7aeb72d7f5c4942adf2697a3220688d8e" + integrity sha1-gdgax663LX9cSUKt8ml6MiBojY4= + request-progress@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" @@ -17757,7 +18448,7 @@ request-promise-native@^1.0.5, request-promise-native@^1.0.8: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@2.88.2, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -18109,6 +18800,13 @@ rxjs@^6.5.2: dependencies: tslib "^1.9.0" +rxjs@^6.6.0: + version "6.6.2" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2" + integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -18211,6 +18909,11 @@ screenfull@^5.0.0: resolved "https://registry.npmjs.org/screenfull/-/screenfull-5.0.2.tgz#b9acdcf1ec676a948674df5cd0ff66b902b0bed7" integrity sha512-cCF2b+L/mnEiORLN5xSAz6H3t18i2oHh9BA8+CQlAh5DRw2+NFAGQJOSYbcGw8B2k04g/lVvFcfZ83b3ysH5UQ== +scuid@^1.0.2: + version "1.1.0" + resolved "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz#d3f9f920956e737a60f72d0e4ad280bf324d5dab" + integrity sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg== + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -18367,7 +19070,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -18485,6 +19188,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +signedsource@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/signedsource/-/signedsource-1.0.0.tgz#1ddace4981798f93bd833973803d80d52e93ad6a" + integrity sha1-HdrOSYF5j5O9gzlzgD2A1S6TrWo= + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -19977,7 +20685,12 @@ ts-loader@^7.0.4: micromatch "^4.0.0" semver "^6.0.0" -ts-node@^8.6.2: +ts-log@2.1.4: + version "2.1.4" + resolved "https://registry.npmjs.org/ts-log/-/ts-log-2.1.4.tgz#063c5ad1cbab5d49d258d18015963489fb6fb59a" + integrity sha512-P1EJSoyV+N3bR/IWFeAqXzKPZwHpnLY6j7j58mAvewHRipo+BQM2Y1f9Y9BjEQznKwgqqZm7H8iuixmssU7tYQ== + +ts-node@^8.10.2, ts-node@^8.6.2: version "8.10.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== @@ -20126,6 +20839,11 @@ typescript@^3.9.3: resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== +ua-parser-js@^0.7.18: + version "0.7.21" + resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" + integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" @@ -20369,6 +21087,13 @@ update-notifier@^4.0.0: semver-diff "^3.1.1" xdg-basedir "^4.0.0" +upper-case@2.0.1, upper-case@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/upper-case/-/upper-case-2.0.1.tgz#6214d05e235dc817822464ccbae85822b3d8665f" + integrity sha512-laAsbea9SY5osxrv7S99vH9xAaJKrw5Qpdh4ENRLcaxipjKsiaBwiAsxfa8X5mObKNTQPsupSq0J/VIxsSJe3A== + dependencies: + tslib "^1.10.0" + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -20641,6 +21366,14 @@ vscode-languageserver-types@^3.15.1: resolved "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== +vue-template-compiler@^2.6.11: + version "2.6.11" + resolved "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080" + integrity sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA== + dependencies: + de-indent "^1.0.2" + he "^1.1.0" + w3c-hr-time@^1.0.1, w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" @@ -20875,15 +21608,20 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" +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.0.0, whatwg-fetch@^3.0.0: version "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.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@>=0.10.0: + version "3.3.1" + resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.3.1.tgz#6c1acf37dec176b0fd6bc9a74b616bec2f612935" + integrity sha512-faXTmGDcLuEPBpJwb5LQfyxvubKiE+RlbmmweFGKjvIPFj4uHTTfdtTIkdTRhC6OSH9S9eyYbx8kZ0UEaQqYTA== whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" @@ -21027,6 +21765,15 @@ worker-rpc@^0.1.0: dependencies: microevent.ts "~0.1.1" +wrap-ansi@7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -21259,12 +22006,17 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml-ast-parser@^0.0.40: + version "0.0.40" + resolved "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.40.tgz#08536d4e73d322b1c9ce207ab8dd70e04d20ae6e" + integrity sha1-CFNtTnPTIrHJziB6uN1w4E0grm4= + yaml@*, yaml@^1.10.0, yaml@^1.7.2, yaml@^1.9.2: version "1.10.0" resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== -yargs-parser@18.x, yargs-parser@^18.1.1: +yargs-parser@18.x, yargs-parser@^18.1.1, yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== @@ -21303,6 +22055,23 @@ yargs-parser@^3.2.0: camelcase "^3.0.0" lodash.assign "^4.1.0" +yargs@15.4.1: + version "15.4.1" + resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yargs@^13.3.0, yargs@^13.3.2: version "13.3.2" resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" From f21b65561bf8163a0fce03ffdd8928232877cdb9 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 10:37:39 +0200 Subject: [PATCH 007/219] chore(gql): fixing dependencies now there is a new release --- plugins/catalog-graphql/package.json | 10 +++++----- yarn.lock | 12 +++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 8a0c0474f6..f64ba1404e 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graphql", - "version": "0.1.1-alpha.16", + "version": "0.1.1-alpha.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,9 +22,9 @@ "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.16", - "@backstage/catalog-model": "^0.1.1-alpha.16", - "@backstage/config": "^0.1.1-alpha.16", + "@backstage/backend-common": "^0.1.1-alpha.17", + "@backstage/catalog-model": "^0.1.1-alpha.17", + "@backstage/config": "^0.1.1-alpha.17", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", @@ -38,7 +38,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/cli": "^0.1.1-alpha.17", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.19.5", diff --git a/yarn.lock b/yarn.lock index bbb0a0d3c9..8305285e7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8898,17 +8898,15 @@ depd@~1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -<<<<<<< HEAD -dependency-graph@0.9.0: - version "0.9.0" - resolved "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.9.0.tgz#11aed7e203bc8b00f48356d92db27b265c445318" - integrity sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w== -======= depd@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== ->>>>>>> 24ed336579226fc628f7f6ba38c0bb27f430b257 + +dependency-graph@0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.9.0.tgz#11aed7e203bc8b00f48356d92db27b265c445318" + integrity sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w== deprecated-decorator@^0.1.6: version "0.1.6" From 3bc0ff4dfbf22c3f603aabdea951726769ccbc95 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 10:38:06 +0200 Subject: [PATCH 008/219] chore(gql): make catalog-gql non-private --- plugins/catalog-graphql/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index f64ba1404e..205e35aba6 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -4,7 +4,6 @@ "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", - "private": true, "publishConfig": { "access": "public", "main": "dist/index.esm.js", From e93e414fdd8150dc6dc1515d0475e3aeca996f3d Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 14:46:00 +0200 Subject: [PATCH 009/219] chore(gql): fixing build issues --- plugins/catalog-graphql/package.json | 1 - plugins/graphql/README.md | 12 +--- plugins/graphql/package.json | 3 +- plugins/graphql/src/index.ts | 2 - plugins/graphql/src/run.ts | 33 ---------- .../graphql/src/service/standaloneServer.ts | 62 ------------------ plugins/graphql/src/setupTests.ts | 1 - yarn.lock | 63 ++----------------- 8 files changed, 9 insertions(+), 168 deletions(-) delete mode 100644 plugins/graphql/src/run.ts delete mode 100644 plugins/graphql/src/service/standaloneServer.ts diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 205e35aba6..6f3c2c9aaa 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -29,7 +29,6 @@ "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", - "@types/whatwg-fetch": "^0.0.33", "graphql": "^15.3.0", "node-fetch": "^2.6.0", "ts-node": "^8.10.2", diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md index 3f85ec766e..d5eef26169 100644 --- a/plugins/graphql/README.md +++ b/plugins/graphql/README.md @@ -1,13 +1,3 @@ # 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. +## TODO diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 0cc85d2513..f640c6267b 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -30,7 +30,8 @@ "express": "^4.17.1", "express-promise-router": "^3.0.3", "graphql": "^15.3.0", - "whatwg-fetch": "^2.0.0", + "node-fetch": "^2.6.0", + "reflect-metadata": "^0.1.13", "winston": "^3.2.1", "yn": "^4.0.0" }, diff --git a/plugins/graphql/src/index.ts b/plugins/graphql/src/index.ts index f12e894c84..7612c392a2 100644 --- a/plugins/graphql/src/index.ts +++ b/plugins/graphql/src/index.ts @@ -14,6 +14,4 @@ * 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 deleted file mode 100644 index b96989e4b8..0000000000 --- a/plugins/graphql/src/run.ts +++ /dev/null @@ -1,33 +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 { 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/standaloneServer.ts b/plugins/graphql/src/service/standaloneServer.ts deleted file mode 100644 index 0f772a583f..0000000000 --- a/plugins/graphql/src/service/standaloneServer.ts +++ /dev/null @@ -1,62 +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. - */ -/* - * 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 index ac41ef995a..ba33cf996b 100644 --- a/plugins/graphql/src/setupTests.ts +++ b/plugins/graphql/src/setupTests.ts @@ -13,6 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -require('whatwg-fetch'); export {}; diff --git a/yarn.lock b/yarn.lock index 8305285e7e..ecfbbfa4ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5174,20 +5174,6 @@ dependencies: "@types/node" "*" -"@types/whatwg-fetch@^0.0.33": - version "0.0.33" - resolved "https://registry.npmjs.org/@types/whatwg-fetch/-/whatwg-fetch-0.0.33.tgz#19c0d2863c8cb2380f21a1c736b79cbf7895bb13" - integrity sha1-GcDShjyMsjgPIaHHNrecv3iVuxM= - dependencies: - "@types/whatwg-streams" "*" - -"@types/whatwg-streams@*": - version "3.2.1" - resolved "https://registry.npmjs.org/@types/whatwg-streams/-/whatwg-streams-3.2.1.tgz#225009a823e8b44be12569a24e60374ccdabe29e" - integrity sha512-Syv05sRL25b8cC8tqgXSQgLZZmqGq2GO+NafrtHbjPJccP6gWBXmHvo2Trw3AWXQ4QLIkVuOB7uStCuhzswyiw== - dependencies: - whatwg-streams "*" - "@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" @@ -6749,11 +6735,6 @@ bluebird@3.7.2, bluebird@^3.3.5, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5 resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bluebird@~1.0.0: - version "1.0.8" - resolved "https://registry.npmjs.org/bluebird/-/bluebird-1.0.8.tgz#851c7825e6cce59e4b43dde95d574b88675463fc" - integrity sha1-hRx4JebM5Z5LQ93pXVdLiGdUY/w= - bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -8767,11 +8748,6 @@ deep-equal@^1.0.1, deep-equal@^1.1.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" -deep-equal@~0.1.0: - version "0.1.2" - resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz#b246c2b80a570a47c11be1d9bd1070ec878b87ce" - integrity sha1-skbCuApXCkfBG+HZvRBw7IeLh84= - deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -8846,11 +8822,6 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -defined@~0.0.0: - version "0.0.0" - resolved "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz#f35eea7d705e933baf13b2f03b3f83d921403b3e" - integrity sha1-817qfXBekzuvE7LwOz+D2SFAOz4= - del@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" @@ -18113,6 +18084,11 @@ redux@^4.0.4: loose-envify "^1.4.0" symbol-observable "^1.2.0" +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + refractor@^2.4.1: version "2.10.1" resolved "https://registry.npmjs.org/refractor/-/refractor-2.10.1.tgz#166c32f114ed16fd96190ad21d5193d3afc7d34e" @@ -18490,13 +18466,6 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -resumer@~0.0.0: - version "0.0.0" - resolved "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" - integrity sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k= - dependencies: - through "~2.3.4" - ret@~0.1.10: version "0.1.15" resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -20065,18 +20034,6 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tape@~2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/tape/-/tape-2.3.3.tgz#2e7ce0a31df09f8d6851664a71842e0ca5057af7" - integrity sha1-Lnzgox3wn41oUWZKcYQuDKUFevc= - dependencies: - deep-equal "~0.1.0" - defined "~0.0.0" - inherits "~2.0.1" - jsonify "~0.0.0" - resumer "~0.0.0" - through "~2.3.4" - tar-fs@^1.16.3: version "1.16.3" resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" @@ -20319,7 +20276,7 @@ through2@^3.0.0: dependencies: readable-stream "2 || 3" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1, through@~2.3.4: +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -21532,14 +21489,6 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -whatwg-streams@*: - version "0.1.1" - resolved "https://registry.npmjs.org/whatwg-streams/-/whatwg-streams-0.1.1.tgz#dd5575c965bce13da76dc0dd495629a87572e74e" - integrity sha1-3VV1yWW84T2nbcDdSVYpqHVy504= - dependencies: - bluebird "~1.0.0" - tape "~2.3.2" - whatwg-url@^6.4.1: version "6.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" From 679cc1c72b2cc5d9a897cf1e139a5605943e4322 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 15:22:58 +0200 Subject: [PATCH 010/219] chore(gql): add todo --- plugins/catalog-graphql/src/service/client.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts index 091a8a8cc0..eddae2f396 100644 --- a/plugins/catalog-graphql/src/service/client.ts +++ b/plugins/catalog-graphql/src/service/client.ts @@ -22,6 +22,8 @@ export class CatalogClient { async list(): Promise { const res = await fetch(`${this.baseUrl}/catalog/entities`); if (!res.ok) { + // todo(blam): need some better way to handle errors here + // experiment with throwing the input errors etc and having graphql versions of that throw new Error(`NOPE, ${await res.text()}`); } From 06626e171ff9ad5c6e5fca2eba6e6ea11603ec52 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 15:36:21 +0200 Subject: [PATCH 011/219] chore(gql): fixing the actual build issue --- plugins/catalog-graphql/package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 6f3c2c9aaa..fec113f545 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -28,7 +28,6 @@ "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", - "@types/express": "^4.17.6", "graphql": "^15.3.0", "node-fetch": "^2.6.0", "ts-node": "^8.10.2", @@ -39,6 +38,7 @@ "@backstage/cli": "^0.1.1-alpha.17", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", + "@types/express": "^4.17.7", "msw": "^0.19.5", "supertest": "^4.0.2" }, diff --git a/yarn.lock b/yarn.lock index ecfbbfa4ea..a862980d7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4458,7 +4458,7 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express@*", "@types/express@^4.17.6": +"@types/express@*", "@types/express@^4.17.6", "@types/express@^4.17.7": version "4.17.7" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59" integrity sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ== From e0bbbae89816e14251fddd84500d6b79984542d1 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 16:02:28 +0200 Subject: [PATCH 012/219] feat(gql): move out the generation into a one time command --- plugins/catalog-graphql/.gitignore | 1 - plugins/catalog-graphql/package.json | 2 +- plugins/catalog-graphql/src/graphql/types.ts | 209 +++++++++++++++++++ 3 files changed, 210 insertions(+), 2 deletions(-) delete mode 100644 plugins/catalog-graphql/.gitignore create mode 100644 plugins/catalog-graphql/src/graphql/types.ts diff --git a/plugins/catalog-graphql/.gitignore b/plugins/catalog-graphql/.gitignore deleted file mode 100644 index 5a5538170a..0000000000 --- a/plugins/catalog-graphql/.gitignore +++ /dev/null @@ -1 +0,0 @@ -src/graphql/types.ts diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index fec113f545..cb8f900bff 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -16,7 +16,7 @@ "test": "backstage-cli test", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", - "postinstall": "graphql-codegen", + "generate:types": "graphql-codegen", "clean": "backstage-cli clean", "mock-data": "./scripts/mock-data.sh" }, diff --git a/plugins/catalog-graphql/src/graphql/types.ts b/plugins/catalog-graphql/src/graphql/types.ts new file mode 100644 index 0000000000..567e19c8bb --- /dev/null +++ b/plugins/catalog-graphql/src/graphql/types.ts @@ -0,0 +1,209 @@ +/* + * 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 { GraphQLResolveInfo } from 'graphql'; +import { ModuleContext } from '@graphql-modules/core'; + +export type Maybe = T | null; +export type Exact = { [K in keyof T]: T[K] }; +export type RequireFields = { [X in Exclude]?: T[X] } & { [P in K]-?: NonNullable }; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; +}; + +export type EntityMetadataAnnotation = { + __typename?: 'EntityMetadataAnnotation'; + key: Scalars['String']; + value: Scalars['String']; +}; + +export type EntityMetadata = { + __typename?: 'EntityMetadata'; + name: Scalars['String']; + namespace?: Maybe; + annotation?: Maybe; + annotations?: Maybe>; +}; + + +export type EntityMetadataAnnotationArgs = { + name?: Maybe; +}; + +export type CatalogEntity = { + __typename?: 'CatalogEntity'; + apiVersion?: Maybe; + kind: Scalars['String']; + metadata?: Maybe; +}; + +export type CatalogQuery = { + __typename?: 'CatalogQuery'; + list: Array; +}; + +export type Query = { + __typename?: 'Query'; + catalog: CatalogQuery; +}; + +export type WithIndex = TObject & Record; +export type ResolversObject = WithIndex; + +export type ResolverTypeWrapper = Promise | T; + + +export type LegacyStitchingResolver = { + fragment: string; + resolve: ResolverFn; +}; + +export type NewStitchingResolver = { + selectionSet: string; + resolve: ResolverFn; +}; +export type StitchingResolver = LegacyStitchingResolver | NewStitchingResolver; +export type Resolver = + | ResolverFn + | StitchingResolver; + +export type ResolverFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => Promise | TResult; + +export type SubscriptionSubscribeFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => AsyncIterator | Promise>; + +export type SubscriptionResolveFn = ( + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => TResult | Promise; + +export interface SubscriptionSubscriberObject { + subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; + resolve?: SubscriptionResolveFn; +} + +export interface SubscriptionResolverObject { + subscribe: SubscriptionSubscribeFn; + resolve: SubscriptionResolveFn; +} + +export type SubscriptionObject = + | SubscriptionSubscriberObject + | SubscriptionResolverObject; + +export type SubscriptionResolver = + | ((...args: any[]) => SubscriptionObject) + | SubscriptionObject; + +export type TypeResolveFn = ( + parent: TParent, + context: TContext, + info: GraphQLResolveInfo +) => Maybe | Promise>; + +export type IsTypeOfResolverFn = (obj: T, info: GraphQLResolveInfo) => boolean | Promise; + +export type NextResolverFn = () => Promise; + +export type DirectiveResolverFn = ( + next: NextResolverFn, + parent: TParent, + args: TArgs, + context: TContext, + info: GraphQLResolveInfo +) => TResult | Promise; + +/** Mapping between all available schema types and the resolvers types */ +export type ResolversTypes = ResolversObject<{ + EntityMetadataAnnotation: ResolverTypeWrapper>; + String: ResolverTypeWrapper>; + EntityMetadata: ResolverTypeWrapper>; + CatalogEntity: ResolverTypeWrapper>; + CatalogQuery: ResolverTypeWrapper>; + Query: ResolverTypeWrapper<{}>; + Boolean: ResolverTypeWrapper>; +}>; + +/** Mapping between all available schema types and the resolvers parents */ +export type ResolversParentTypes = ResolversObject<{ + EntityMetadataAnnotation: Partial; + String: Partial; + EntityMetadata: Partial; + CatalogEntity: Partial; + CatalogQuery: Partial; + Query: {}; + Boolean: Partial; +}>; + +export type EntityMetadataAnnotationResolvers = ResolversObject<{ + key?: Resolver; + value?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type EntityMetadataResolvers = ResolversObject<{ + name?: Resolver; + namespace?: Resolver, ParentType, ContextType>; + annotation?: Resolver, ParentType, ContextType, RequireFields>; + annotations?: Resolver>, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type CatalogEntityResolvers = ResolversObject<{ + apiVersion?: Resolver, ParentType, ContextType>; + kind?: Resolver; + metadata?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type CatalogQueryResolvers = ResolversObject<{ + list?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type QueryResolvers = ResolversObject<{ + catalog?: Resolver; +}>; + +export type Resolvers = ResolversObject<{ + EntityMetadataAnnotation?: EntityMetadataAnnotationResolvers; + EntityMetadata?: EntityMetadataResolvers; + CatalogEntity?: CatalogEntityResolvers; + CatalogQuery?: CatalogQueryResolvers; + Query?: QueryResolvers; +}>; + + +/** + * @deprecated + * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. + */ +export type IResolvers = Resolvers; From b72f136c3657325a02b9c7b4831961747eafcdfd Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 6 Aug 2020 16:21:57 +0200 Subject: [PATCH 013/219] test(gql): added some mock tests for now --- .../src/service/client.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 plugins/catalog-graphql/src/service/client.test.ts diff --git a/plugins/catalog-graphql/src/service/client.test.ts b/plugins/catalog-graphql/src/service/client.test.ts new file mode 100644 index 0000000000..e249bf1c15 --- /dev/null +++ b/plugins/catalog-graphql/src/service/client.test.ts @@ -0,0 +1,23 @@ +/* + * 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 { CatalogClient } from './client'; + +describe('Catalog GraphQL Module', () => { + it('create a new client', () => { + void new CatalogClient('http://localhost:1234'); + expect(true).toBe(true); + }); +}); From 0fe6f44c17c0a472976308ecbc879c7bad03f75f Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 10:32:18 +0200 Subject: [PATCH 014/219] chore(gql/catalog): updating the version deps after new buymp --- plugins/catalog-graphql/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index cb8f900bff..7c8b73a943 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graphql", - "version": "0.1.1-alpha.17", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.17", - "@backstage/catalog-model": "^0.1.1-alpha.17", - "@backstage/config": "^0.1.1-alpha.17", + "@backstage/backend-common": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.18", + "@backstage/config": "^0.1.1-alpha.18", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.17", + "@backstage/cli": "^0.1.1-alpha.18", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "@types/express": "^4.17.7", From 219fa73ad0b8603d60982f496e5aca8474f93e72 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 10:51:46 +0200 Subject: [PATCH 015/219] feat(gql/catlaog): Tidying up the resolver for the annotations --- plugins/catalog-graphql/package.json | 3 +- plugins/catalog-graphql/src/graphql/module.ts | 23 +++++--------- plugins/catalog-graphql/src/schema.gql | 6 ++-- yarn.lock | 31 ++++++------------- 4 files changed, 22 insertions(+), 41 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 7c8b73a943..be5c585186 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -29,6 +29,7 @@ "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", "graphql": "^15.3.0", + "graphql-type-json": "^0.3.2", "node-fetch": "^2.6.0", "ts-node": "^8.10.2", "winston": "^3.2.1", @@ -36,9 +37,9 @@ }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.18", + "@types/express": "^4.17.7", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", - "@types/express": "^4.17.7", "msw": "^0.19.5", "supertest": "^4.0.2" }, diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 1fc13d7950..2f9df09408 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -18,26 +18,16 @@ import { Logger } from 'winston'; import fs from 'fs'; import path from 'path'; import { GraphQLModule } from '@graphql-modules/core'; -import { Resolvers, CatalogEntity, CatalogQuery } from './types'; -import { Entity } from '@backstage/catalog-model'; +import { Resolvers, CatalogQuery } from './types'; import { Config } from '@backstage/config'; import { CatalogClient } from '../service/client'; +import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json'; export interface ModuleOptions { logger: Logger; config: Config; } -const parseToCatalogEntities = (e: Entity): CatalogEntity => ({ - ...e, - metadata: { - ...e.metadata, - annotations: Object.entries( - e.metadata.annotations ?? {}, - ).map(([key, value]) => ({ key, value })), - }, -}); - export async function createModule( options: ModuleOptions, ): Promise { @@ -51,18 +41,19 @@ export async function createModule( ); const resolvers: Resolvers = { + JSON: GraphQLJSON, + JSONObject: GraphQLJSONObject, Query: { catalog: () => ({} as CatalogQuery), }, CatalogQuery: { list: async () => { - const list = await catalogClient.list(); - return list.map(parseToCatalogEntities); + return await catalogClient.list(); }, }, EntityMetadata: { - annotation: (e, { name }) => - e.annotations?.find(a => a.key === name) ?? null, + annotations: e => e.annotations, + annotation: (e, { name }) => e.annotations[name] ?? null, }, }; diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/src/schema.gql index b28f796855..5b2ee51db0 100644 --- a/plugins/catalog-graphql/src/schema.gql +++ b/plugins/catalog-graphql/src/schema.gql @@ -1,3 +1,6 @@ +scalar JSON +scalar JSONObject + type EntityMetadataAnnotation { key: String! value: String! @@ -6,8 +9,7 @@ type EntityMetadataAnnotation { type EntityMetadata { name: String! namespace: String - annotation(name: String): EntityMetadataAnnotation - annotations: [EntityMetadataAnnotation!] + annotations: JSONObject } type CatalogEntity { diff --git a/yarn.lock b/yarn.lock index 83b287dd4a..e967be54ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10379,7 +10379,6 @@ file-loader@^4.2.0: "file-saver@github:eligrey/FileSaver.js#1.3.8": version "1.3.8" - uid e865e37af9f9947ddcced76b549e27dc45c1cb2e resolved "https://codeload.github.com/eligrey/FileSaver.js/tar.gz/e865e37af9f9947ddcced76b549e27dc45c1cb2e" file-system-cache@^1.0.5: @@ -11376,6 +11375,11 @@ graphql-tools@^4.0.0: iterall "^1.1.3" uuid "^3.1.0" +graphql-type-json@^0.3.2: + version "0.3.2" + resolved "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115" + integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg== + graphql-upload@^8.0.2: version "8.1.0" resolved "https://registry.npmjs.org/graphql-upload/-/graphql-upload-8.1.0.tgz#6d0ab662db5677a68bfb1f2c870ab2544c14939a" @@ -13586,7 +13590,6 @@ jsonpointer@^4.0.1: resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= -<<<<<<< HEAD jsonwebtoken@^8.1.0: version "8.5.1" resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" @@ -13602,7 +13605,7 @@ jsonwebtoken@^8.1.0: lodash.once "^4.0.0" ms "^2.1.1" semver "^5.6.0" -======= + jspdf-autotable@3.5.3: version "3.5.3" resolved "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.5.3.tgz#2f73adb07f340e7dbf22950e3e6c8bf853991479" @@ -13614,12 +13617,11 @@ jspdf@1.5.3: integrity sha512-J9X76xnncMw+wIqb15HeWfPMqPwYxSpPY8yWPJ7rAZN/ZDzFkjCSZObryCyUe8zbrVRNiuCnIeQteCzMn7GnWw== dependencies: canvg "1.5.3" - file-saver "github:eligrey/FileSaver.js#1.3.8" + file-saver eligrey/FileSaver.js#1.3.8 html2canvas "1.0.0-alpha.12" omggif "1.0.7" promise-polyfill "8.1.0" stackblur-canvas "2.2.0" ->>>>>>> f225b094e4651d478fa5925ef498fdbdab6ca353 jsprim@^1.2.2: version "1.4.1" @@ -16072,9 +16074,6 @@ parse-entities@^1.1.0, parse-entities@^1.1.2: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" -<<<<<<< HEAD -parse-filepath@1.0.2, parse-filepath@^1.0.1: -======= parse-entities@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" @@ -16087,8 +16086,7 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" -parse-filepath@^1.0.1: ->>>>>>> f225b094e4651d478fa5925ef498fdbdab6ca353 +parse-filepath@1.0.2, parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= @@ -17149,7 +17147,6 @@ pretty-hrtime@^1.0.3: resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= -<<<<<<< HEAD prisma-json-schema@0.1.3: version "0.1.3" resolved "https://registry.npmjs.org/prisma-json-schema/-/prisma-json-schema-0.1.3.tgz#6c302db8f464f8b92e8694d3f7dd3f41ac9afcbe" @@ -17179,16 +17176,10 @@ prisma-yml@1.34.10: scuid "^1.0.2" yaml-ast-parser "^0.0.40" -prismjs@^1.8.4: - version "1.19.0" - resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.19.0.tgz#713afbd45c3baca4b321569f2df39e17e729d4dc" - integrity sha512-IVFtbW9mCWm9eOIaEkNyo2Vl4NnEifis2GQ7/MLRG5TQe6t+4Sj9J5QWI9i3v+SS43uZBlCAOn+zYTVYQcPXJw== -======= prismjs@^1.20.0, prismjs@^1.8.4, prismjs@~1.20.0: version "1.20.0" resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz#9b685fc480a3514ee7198eac6a3bf5024319ff03" integrity sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ== ->>>>>>> f225b094e4651d478fa5925ef498fdbdab6ca353 optionalDependencies: clipboard "^2.0.0" @@ -18481,11 +18472,7 @@ request-promise-native@^1.0.5, request-promise-native@^1.0.8: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -<<<<<<< HEAD -request@2.88.2, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: -======= -request@^2.55.0, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: ->>>>>>> f225b094e4651d478fa5925ef498fdbdab6ca353 +request@2.88.2, request@^2.55.0, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== From f5eb812847a903d1e1381e59630c8e534dc34053 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:00:55 +0200 Subject: [PATCH 016/219] chore(gql/catalog): Added some more metadata fields to the catalog entities --- plugins/catalog-graphql/src/schema.gql | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/src/schema.gql index 5b2ee51db0..02abffa09e 100644 --- a/plugins/catalog-graphql/src/schema.gql +++ b/plugins/catalog-graphql/src/schema.gql @@ -8,14 +8,19 @@ type EntityMetadataAnnotation { type EntityMetadata { name: String! - namespace: String + namespace: String! annotations: JSONObject + annotation(name: String): JSON + labels: JSONObject + uid: String! + etag: String! + generation: Int! } type CatalogEntity { - apiVersion: String + apiVersion: String! kind: String! - metadata: EntityMetadata + metadata: EntityMetadata! } type CatalogQuery { From 4d4ead15c4f3e168e29b0896b83a1a3352c5b345 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:09:30 +0200 Subject: [PATCH 017/219] chore(gql/catalog): making the client alittle more readabke --- plugins/catalog-graphql/src/service/client.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts index eddae2f396..f7bc5ccffe 100644 --- a/plugins/catalog-graphql/src/service/client.ts +++ b/plugins/catalog-graphql/src/service/client.ts @@ -27,6 +27,7 @@ export class CatalogClient { throw new Error(`NOPE, ${await res.text()}`); } - return await (res.json() as Promise); + const entities: Entity[] = await res.json(); + return entities; } } From 8e1cb1ca0be466c3f1a1849e37c591a536aeae8a Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:37:30 +0200 Subject: [PATCH 018/219] feat(gql/catalog): Fixing some of the query issues --- plugins/catalog-graphql/src/graphql/module.ts | 10 ++- plugins/catalog-graphql/src/graphql/types.ts | 74 ++++++++++++------- plugins/catalog-graphql/src/schema.gql | 12 +-- plugins/catalog-graphql/src/service/client.ts | 18 ++++- 4 files changed, 74 insertions(+), 40 deletions(-) diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 2f9df09408..6deb2a3aad 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -48,7 +48,15 @@ export async function createModule( }, CatalogQuery: { list: async () => { - return await catalogClient.list(); + const catalogEntries = await catalogClient.list(); + return catalogEntries.map(entity => ({ + ...entity, + metadata: { + ...entity.metadata, + annotations: entity.metadata.annotations ?? {}, + labels: entity.metadata.labels ?? {}, + }, + })); }, }, EntityMetadata: { diff --git a/plugins/catalog-graphql/src/graphql/types.ts b/plugins/catalog-graphql/src/graphql/types.ts index 567e19c8bb..7294f749ad 100644 --- a/plugins/catalog-graphql/src/graphql/types.ts +++ b/plugins/catalog-graphql/src/graphql/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { GraphQLResolveInfo } from 'graphql'; +import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; import { ModuleContext } from '@graphql-modules/core'; export type Maybe = T | null; @@ -26,32 +26,40 @@ export type Scalars = { Boolean: boolean; Int: number; Float: number; + JSON: any; + JSONObject: any; }; -export type EntityMetadataAnnotation = { - __typename?: 'EntityMetadataAnnotation'; - key: Scalars['String']; - value: Scalars['String']; -}; + export type EntityMetadata = { __typename?: 'EntityMetadata'; name: Scalars['String']; - namespace?: Maybe; - annotation?: Maybe; - annotations?: Maybe>; + namespace: Scalars['String']; + annotations: Scalars['JSONObject']; + annotation?: Maybe; + labels: Scalars['JSONObject']; + label?: Maybe; + uid: Scalars['String']; + etag: Scalars['String']; + generation: Scalars['Int']; }; export type EntityMetadataAnnotationArgs = { - name?: Maybe; + name: Scalars['String']; +}; + + +export type EntityMetadataLabelArgs = { + name: Scalars['String']; }; export type CatalogEntity = { __typename?: 'CatalogEntity'; - apiVersion?: Maybe; + apiVersion: Scalars['String']; kind: Scalars['String']; - metadata?: Maybe; + metadata: EntityMetadata; }; export type CatalogQuery = { @@ -143,9 +151,11 @@ export type DirectiveResolverFn>; - String: ResolverTypeWrapper>; + JSON: ResolverTypeWrapper>; + JSONObject: ResolverTypeWrapper>; EntityMetadata: ResolverTypeWrapper>; + String: ResolverTypeWrapper>; + Int: ResolverTypeWrapper>; CatalogEntity: ResolverTypeWrapper>; CatalogQuery: ResolverTypeWrapper>; Query: ResolverTypeWrapper<{}>; @@ -154,33 +164,42 @@ export type ResolversTypes = ResolversObject<{ /** Mapping between all available schema types and the resolvers parents */ export type ResolversParentTypes = ResolversObject<{ - EntityMetadataAnnotation: Partial; - String: Partial; + JSON: Partial; + JSONObject: Partial; EntityMetadata: Partial; + String: Partial; + Int: Partial; CatalogEntity: Partial; CatalogQuery: Partial; Query: {}; Boolean: Partial; }>; -export type EntityMetadataAnnotationResolvers = ResolversObject<{ - key?: Resolver; - value?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; +export interface JsonScalarConfig extends GraphQLScalarTypeConfig { + name: 'JSON'; +} + +export interface JsonObjectScalarConfig extends GraphQLScalarTypeConfig { + name: 'JSONObject'; +} export type EntityMetadataResolvers = ResolversObject<{ name?: Resolver; - namespace?: Resolver, ParentType, ContextType>; - annotation?: Resolver, ParentType, ContextType, RequireFields>; - annotations?: Resolver>, ParentType, ContextType>; + namespace?: Resolver; + annotations?: Resolver; + annotation?: Resolver, ParentType, ContextType, RequireFields>; + labels?: Resolver; + label?: Resolver, ParentType, ContextType, RequireFields>; + uid?: Resolver; + etag?: Resolver; + generation?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; export type CatalogEntityResolvers = ResolversObject<{ - apiVersion?: Resolver, ParentType, ContextType>; + apiVersion?: Resolver; kind?: Resolver; - metadata?: Resolver, ParentType, ContextType>; + metadata?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -194,7 +213,8 @@ export type QueryResolvers; export type Resolvers = ResolversObject<{ - EntityMetadataAnnotation?: EntityMetadataAnnotationResolvers; + JSON?: GraphQLScalarType; + JSONObject?: GraphQLScalarType; EntityMetadata?: EntityMetadataResolvers; CatalogEntity?: CatalogEntityResolvers; CatalogQuery?: CatalogQueryResolvers; diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/src/schema.gql index 02abffa09e..bb8e5bcc87 100644 --- a/plugins/catalog-graphql/src/schema.gql +++ b/plugins/catalog-graphql/src/schema.gql @@ -1,17 +1,13 @@ scalar JSON scalar JSONObject -type EntityMetadataAnnotation { - key: String! - value: String! -} - type EntityMetadata { name: String! namespace: String! - annotations: JSONObject - annotation(name: String): JSON - labels: JSONObject + annotations: JSONObject! + annotation(name: String!): JSON + labels: JSONObject! + label(name: String!): JSON uid: String! etag: String! generation: Int! diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts index f7bc5ccffe..05e73b88b8 100644 --- a/plugins/catalog-graphql/src/service/client.ts +++ b/plugins/catalog-graphql/src/service/client.ts @@ -14,20 +14,30 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, EntityMeta } from '@backstage/catalog-model'; import fetch from 'node-fetch'; +import { JsonObject } from '@backstage/config'; +export interface ReaderEntityMeta extends EntityMeta { + uid: string; + etag: string; + generation: number; + namespace: string; +} +export interface ReaderEntity extends Entity { + metadata: JsonObject & ReaderEntityMeta; +} export class CatalogClient { constructor(private baseUrl: string) {} - async list(): Promise { + async list(): Promise { const res = await fetch(`${this.baseUrl}/catalog/entities`); if (!res.ok) { // todo(blam): need some better way to handle errors here // experiment with throwing the input errors etc and having graphql versions of that - throw new Error(`NOPE, ${await res.text()}`); + throw new Error(await res.text()); } - const entities: Entity[] = await res.json(); + const entities: ReaderEntity[] = await res.json(); return entities; } } From 042599d882ff3d10d1022fd854ba734d0dae3ca4 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:42:07 +0200 Subject: [PATCH 019/219] feat(gql/catalog): implement the label resolver --- plugins/catalog-graphql/src/graphql/module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 6deb2a3aad..772e472d1b 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -60,8 +60,8 @@ export async function createModule( }, }, EntityMetadata: { - annotations: e => e.annotations, - annotation: (e, { name }) => e.annotations[name] ?? null, + annotation: (e, { name }) => e.annotations?.[name] ?? null, + label: (e, { name }) => e.labels?.[name] ?? null, }, }; From be1c5267081fa7e37e48b337888d326cc5120881 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:43:44 +0200 Subject: [PATCH 020/219] chore(gql/catalog): removing the mock-data stuff --- plugins/graphql/package.json | 3 +-- plugins/graphql/scripts/mock-data.sh | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100755 plugins/graphql/scripts/mock-data.sh diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 2b6d9a40e7..f98c21f2c7 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -16,8 +16,7 @@ "test": "backstage-cli test", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean", - "mock-data": "./scripts/mock-data.sh" + "clean": "backstage-cli clean" }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.18", diff --git a/plugins/graphql/scripts/mock-data.sh b/plugins/graphql/scripts/mock-data.sh deleted file mode 100755 index ff30921715..0000000000 --- a/plugins/graphql/scripts/mock-data.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -echo "use this script to load your service with some mock data if needed!" From 8fc45676bf14c9d4389a7fb8934aa7712e0935e6 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:46:54 +0200 Subject: [PATCH 021/219] chore(gql/catalog): fixing bundling of graphql asse --- plugins/catalog-graphql/package.json | 3 ++- plugins/catalog-graphql/src/graphql/module.ts | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index be5c585186..d339119901 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -44,6 +44,7 @@ "supertest": "^4.0.2" }, "files": [ - "dist" + "dist", + "schema.gql" ] } diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 772e472d1b..eb4c550819 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -28,13 +28,15 @@ export interface ModuleOptions { config: Config; } +const schemaPath = path.resolve( + require.resolve('@backstage/plugin-graphql-backend/package.json'), + '../schema.gql', +); + export async function createModule( options: ModuleOptions, ): Promise { - const typeDefs = await fs.promises.readFile( - path.resolve(__dirname, '..', 'schema.gql'), - 'utf-8', - ); + const typeDefs = await fs.promises.readFile(schemaPath, 'utf-8'); const catalogClient = new CatalogClient( options.config.getString('backend.baseUrl'), From c8060220e1842805c25e63cbe42f28b25509579b Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:47:31 +0200 Subject: [PATCH 022/219] chore(gql/catalog): actually read --- plugins/catalog-graphql/src/graphql/module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index eb4c550819..967df97662 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -29,7 +29,7 @@ export interface ModuleOptions { } const schemaPath = path.resolve( - require.resolve('@backstage/plugin-graphql-backend/package.json'), + require.resolve('@backstage/plugin-catalog-graphql/package.json'), '../schema.gql', ); From fc85bb41ddb9c09b468597033a92050a82a79d65 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 11:52:05 +0200 Subject: [PATCH 023/219] chore(gql/catalog): removing the mock data scripts for catalog-graph l --- plugins/catalog-graphql/package.json | 3 +-- plugins/catalog-graphql/scripts/mock-data.sh | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100755 plugins/catalog-graphql/scripts/mock-data.sh diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index d339119901..28e72cefd3 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -17,8 +17,7 @@ "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", "generate:types": "graphql-codegen", - "clean": "backstage-cli clean", - "mock-data": "./scripts/mock-data.sh" + "clean": "backstage-cli clean" }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.18", diff --git a/plugins/catalog-graphql/scripts/mock-data.sh b/plugins/catalog-graphql/scripts/mock-data.sh deleted file mode 100755 index ff30921715..0000000000 --- a/plugins/catalog-graphql/scripts/mock-data.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -echo "use this script to load your service with some mock data if needed!" From c4911612b1a07738cb747f4ad626ec6a66f442fa Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 14:21:10 +0200 Subject: [PATCH 024/219] chore(gql/catalog): updating the return types from the catalog api --- plugins/catalog-graphql/src/graphql/module.ts | 10 +--------- plugins/catalog-graphql/src/service/client.ts | 2 ++ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 967df97662..3068b3b129 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -50,15 +50,7 @@ export async function createModule( }, CatalogQuery: { list: async () => { - const catalogEntries = await catalogClient.list(); - return catalogEntries.map(entity => ({ - ...entity, - metadata: { - ...entity.metadata, - annotations: entity.metadata.annotations ?? {}, - labels: entity.metadata.labels ?? {}, - }, - })); + return await catalogClient.list(); }, }, EntityMetadata: { diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts index 05e73b88b8..8af97a89b1 100644 --- a/plugins/catalog-graphql/src/service/client.ts +++ b/plugins/catalog-graphql/src/service/client.ts @@ -23,6 +23,8 @@ export interface ReaderEntityMeta extends EntityMeta { etag: string; generation: number; namespace: string; + annotations: Record; + labels: Record; } export interface ReaderEntity extends Entity { metadata: JsonObject & ReaderEntityMeta; From 9bcc1bcb288a030c3d2e8ed3ad3fae06a439a9fe Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 16:11:21 +0200 Subject: [PATCH 025/219] chore(catalog/gql): move the catalog schema out of src --- plugins/catalog-graphql/{src => }/schema.gql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/catalog-graphql/{src => }/schema.gql (100%) diff --git a/plugins/catalog-graphql/src/schema.gql b/plugins/catalog-graphql/schema.gql similarity index 100% rename from plugins/catalog-graphql/src/schema.gql rename to plugins/catalog-graphql/schema.gql From 10e4dc991f197322cfad0c9ecca35cd770de98cc Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 16:12:15 +0200 Subject: [PATCH 026/219] chore(catalog/gql): fixing csp for playground --- plugins/graphql/package.json | 1 + plugins/graphql/src/service/router.ts | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index f98c21f2c7..6f8ff50c1a 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -29,6 +29,7 @@ "express": "^4.17.1", "express-promise-router": "^3.0.3", "graphql": "^15.3.0", + "helmet": "^4.0.0", "node-fetch": "^2.6.0", "reflect-metadata": "^0.1.13", "winston": "^3.2.1", diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts index 784b801b86..cd926da3a2 100644 --- a/plugins/graphql/src/service/router.ts +++ b/plugins/graphql/src/service/router.ts @@ -24,6 +24,7 @@ import { GraphQLModule } from '@graphql-modules/core'; import { ApolloServer } from 'apollo-server-express'; import { createModule as createCatalogModule } from '@backstage/plugin-catalog-graphql'; import { Config } from '@backstage/config'; +import helmet from 'helmet'; const schemaPath = path.resolve( require.resolve('@backstage/plugin-graphql-backend/package.json'), @@ -41,15 +42,29 @@ export async function createRouter( const typeDefs = await fs.promises.readFile(schemaPath, 'utf-8'); const catalogModule = await createCatalogModule(options); + const { schema } = new GraphQLModule({ imports: [catalogModule], typeDefs, }); - const server = new ApolloServer({ schema, logger: options.logger }); + const server = new ApolloServer({ + schema, + logger: options.logger, + }); + const router = Router(); const apolloMiddlware = server.getMiddleware({ path: '/' }); + + router.use( + helmet.contentSecurityPolicy({ + directives: { + defaultSrc: ["'self'", "'unsafe-inline'", 'http://*'], + }, + }), + ); + router.use(apolloMiddlware); router.get('/health', (_, response) => { From e01952279e1a5e4ce45b80a028509e30bb657259 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 16:12:35 +0200 Subject: [PATCH 027/219] chore(catalog): yarn lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index e967be54ca..7dfa9e500f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10377,7 +10377,7 @@ file-loader@^4.2.0: loader-utils "^1.2.3" schema-utils "^2.5.0" -"file-saver@github:eligrey/FileSaver.js#1.3.8": +file-saver@eligrey/FileSaver.js#1.3.8: version "1.3.8" resolved "https://codeload.github.com/eligrey/FileSaver.js/tar.gz/e865e37af9f9947ddcced76b549e27dc45c1cb2e" From 90579b9089ca4111ba81316d2985e8eb283fdc44 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Aug 2020 18:50:08 +0200 Subject: [PATCH 028/219] feat(gql/catalog): Reduce the amount of duplication a little for unions --- plugins/catalog-graphql/codegen.yml | 2 +- plugins/catalog-graphql/schema.gql | 38 +++++++++- plugins/catalog-graphql/src/graphql/module.ts | 54 +++++++++++++- plugins/catalog-graphql/src/graphql/types.ts | 72 +++++++++++++++++-- 4 files changed, 156 insertions(+), 10 deletions(-) diff --git a/plugins/catalog-graphql/codegen.yml b/plugins/catalog-graphql/codegen.yml index ebdb34a938..a4f3abcbe4 100644 --- a/plugins/catalog-graphql/codegen.yml +++ b/plugins/catalog-graphql/codegen.yml @@ -1,5 +1,5 @@ overwrite: true -schema: ./src/schema.gql +schema: ./schema.gql require: - ts-node/register/transpile-only generates: diff --git a/plugins/catalog-graphql/schema.gql b/plugins/catalog-graphql/schema.gql index bb8e5bcc87..813b2840a6 100644 --- a/plugins/catalog-graphql/schema.gql +++ b/plugins/catalog-graphql/schema.gql @@ -1,9 +1,43 @@ scalar JSON scalar JSONObject -type EntityMetadata { +interface EntityMetadata { + name: String! + annotations: JSONObject! + annotation(name: String!): JSON + labels: JSONObject! + label(name: String!): JSON + uid: String! + etag: String! + generation: Int! +} + +type ComponentMetadata implements EntityMetadata { + name: String! + annotations: JSONObject! + annotation(name: String!): JSON + labels: JSONObject! + label(name: String!): JSON + uid: String! + etag: String! + generation: Int! + relationships: String! +} + +type TemplateMetadata implements EntityMetadata { + name: String! + annotations: JSONObject! + annotation(name: String!): JSON + labels: JSONObject! + label(name: String!): JSON + uid: String! + etag: String! + generation: Int! + updatedBy: String! +} + +type DefaultEntityMetadata implements EntityMetadata { name: String! - namespace: String! annotations: JSONObject! annotation(name: String!): JSON labels: JSONObject! diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index 3068b3b129..f66ff5073e 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -17,8 +17,14 @@ import { Logger } from 'winston'; import fs from 'fs'; import path from 'path'; +import { makeExecutableSchema } from 'apollo-server'; import { GraphQLModule } from '@graphql-modules/core'; -import { Resolvers, CatalogQuery } from './types'; +import { + Resolvers, + CatalogQuery, + CatalogEntityTypes, + EntityMetadataResolvers, +} from './types'; import { Config } from '@backstage/config'; import { CatalogClient } from '../service/client'; import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json'; @@ -33,6 +39,19 @@ const schemaPath = path.resolve( '../schema.gql', ); +const getSpecTypenameForEntity = (e: { kind?: string }) => { + switch (e.kind) { + case 'Component': + return 'ComponentEntity'; + case 'Location': + return 'LocationEntity'; + case 'Template': + return 'TemplateEntity'; + default: + return null; + } +}; + export async function createModule( options: ModuleOptions, ): Promise { @@ -53,15 +72,46 @@ export async function createModule( return await catalogClient.list(); }, }, + ComponentMetadata: { + relationships: () => 'boop', + }, + TemplateMetadata: { + updatedBy: () => 'blam', + }, + CatalogEntity: { + metadata: e => ({ ...e.metadata!, catalogEntity: e }), + }, EntityMetadata: { + __resolveType: (obj: any) => { + const kind = obj.catalogEntity!.kind; + switch (kind) { + case 'Component': + return 'ComponentMetadata'; + case 'Template': + return 'TemplateMetadata'; + default: + return 'DefaultEntityMetadata'; + } + }, annotation: (e, { name }) => e.annotations?.[name] ?? null, + labels: e => e ?? {}, + annotations: e => e ?? {}, label: (e, { name }) => e.labels?.[name] ?? null, }, }; - const module = new GraphQLModule({ + const schema = makeExecutableSchema({ typeDefs, resolvers, + inheritResolversFromInterfaces: true, + resolverValidationOptions: { + allowResolversNotInSchema: true, + }, + }); + + const module = new GraphQLModule({ + resolverValidationOptions: {}, + extraSchemas: [schema], logger: options.logger as any, }); diff --git a/plugins/catalog-graphql/src/graphql/types.ts b/plugins/catalog-graphql/src/graphql/types.ts index 7294f749ad..e2b10ec6a2 100644 --- a/plugins/catalog-graphql/src/graphql/types.ts +++ b/plugins/catalog-graphql/src/graphql/types.ts @@ -33,9 +33,7 @@ export type Scalars = { export type EntityMetadata = { - __typename?: 'EntityMetadata'; name: Scalars['String']; - namespace: Scalars['String']; annotations: Scalars['JSONObject']; annotation?: Maybe; labels: Scalars['JSONObject']; @@ -55,6 +53,39 @@ export type EntityMetadataLabelArgs = { name: Scalars['String']; }; +export type ComponentMetadata = EntityMetadata & { + __typename?: 'ComponentMetadata'; + relationships: Scalars['String']; +}; + +export type TemplateMetadata = EntityMetadata & { + __typename?: 'TemplateMetadata'; + updatedBy: Scalars['String']; +}; + +export type DefaultEntityMetadata = EntityMetadata & { + __typename?: 'DefaultEntityMetadata'; + name: Scalars['String']; + annotations: Scalars['JSONObject']; + annotation?: Maybe; + labels: Scalars['JSONObject']; + label?: Maybe; + uid: Scalars['String']; + etag: Scalars['String']; + generation: Scalars['Int']; + updatedBy: Scalars['String']; +}; + + +export type DefaultEntityMetadataAnnotationArgs = { + name: Scalars['String']; +}; + + +export type DefaultEntityMetadataLabelArgs = { + name: Scalars['String']; +}; + export type CatalogEntity = { __typename?: 'CatalogEntity'; apiVersion: Scalars['String']; @@ -153,9 +184,12 @@ export type DirectiveResolverFn>; JSONObject: ResolverTypeWrapper>; - EntityMetadata: ResolverTypeWrapper>; + EntityMetadata: ResolversTypes['ComponentMetadata'] | ResolversTypes['TemplateMetadata'] | ResolversTypes['DefaultEntityMetadata']; String: ResolverTypeWrapper>; Int: ResolverTypeWrapper>; + ComponentMetadata: ResolverTypeWrapper>; + TemplateMetadata: ResolverTypeWrapper>; + DefaultEntityMetadata: ResolverTypeWrapper>; CatalogEntity: ResolverTypeWrapper>; CatalogQuery: ResolverTypeWrapper>; Query: ResolverTypeWrapper<{}>; @@ -166,9 +200,12 @@ export type ResolversTypes = ResolversObject<{ export type ResolversParentTypes = ResolversObject<{ JSON: Partial; JSONObject: Partial; - EntityMetadata: Partial; + EntityMetadata: ResolversParentTypes['ComponentMetadata'] | ResolversParentTypes['TemplateMetadata'] | ResolversParentTypes['DefaultEntityMetadata']; String: Partial; Int: Partial; + ComponentMetadata: Partial; + TemplateMetadata: Partial; + DefaultEntityMetadata: Partial; CatalogEntity: Partial; CatalogQuery: Partial; Query: {}; @@ -184,8 +221,8 @@ export interface JsonObjectScalarConfig extends GraphQLScalarTypeConfig = ResolversObject<{ + __resolveType: TypeResolveFn<'ComponentMetadata' | 'TemplateMetadata' | 'DefaultEntityMetadata', ParentType, ContextType>; name?: Resolver; - namespace?: Resolver; annotations?: Resolver; annotation?: Resolver, ParentType, ContextType, RequireFields>; labels?: Resolver; @@ -193,6 +230,28 @@ export type EntityMetadataResolvers; etag?: Resolver; generation?: Resolver; +}>; + +export type ComponentMetadataResolvers = ResolversObject<{ + relationships?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type TemplateMetadataResolvers = ResolversObject<{ + updatedBy?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DefaultEntityMetadataResolvers = ResolversObject<{ + name?: Resolver; + annotations?: Resolver; + annotation?: Resolver, ParentType, ContextType, RequireFields>; + labels?: Resolver; + label?: Resolver, ParentType, ContextType, RequireFields>; + uid?: Resolver; + etag?: Resolver; + generation?: Resolver; + updatedBy?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -216,6 +275,9 @@ export type Resolvers = ResolversObject<{ JSON?: GraphQLScalarType; JSONObject?: GraphQLScalarType; EntityMetadata?: EntityMetadataResolvers; + ComponentMetadata?: ComponentMetadataResolvers; + TemplateMetadata?: TemplateMetadataResolvers; + DefaultEntityMetadata?: DefaultEntityMetadataResolvers; CatalogEntity?: CatalogEntityResolvers; CatalogQuery?: CatalogQueryResolvers; Query?: QueryResolvers; From eb3ed5c0f883973e8e86a4d08156ef417f96529d Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 12 Aug 2020 01:45:47 +0200 Subject: [PATCH 029/219] feat(gql/catalog): working on making the schema nice --- plugins/catalog-graphql/codegen.yml | 7 +- plugins/catalog-graphql/package.json | 2 + plugins/catalog-graphql/schema.gql | 62 --- plugins/catalog-graphql/src/graphql/module.ts | 75 ++- plugins/catalog-graphql/src/graphql/types.ts | 434 +++++++++++++++--- 5 files changed, 403 insertions(+), 177 deletions(-) delete mode 100644 plugins/catalog-graphql/schema.gql diff --git a/plugins/catalog-graphql/codegen.yml b/plugins/catalog-graphql/codegen.yml index a4f3abcbe4..bb50b68101 100644 --- a/plugins/catalog-graphql/codegen.yml +++ b/plugins/catalog-graphql/codegen.yml @@ -1,9 +1,7 @@ overwrite: true -schema: ./schema.gql -require: - - ts-node/register/transpile-only generates: ./src/graphql/types.ts: + schema: ./src/schema.js plugins: - typescript - typescript-resolvers @@ -11,6 +9,7 @@ generates: afterOneFileWrite: - eslint --fix config: - contextType: '@graphql-modules/core#ModuleContext' + contextType: "@graphql-modules/core#ModuleContext" + allowParentTypeOverride: true useIndexSignature: true defaultMapper: Partial<{T}> diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 28e72cefd3..83e88e1d25 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -27,7 +27,9 @@ "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", + "apollo-server": "^2.16.1", "graphql": "^15.3.0", + "graphql-tag": "^2.11.0", "graphql-type-json": "^0.3.2", "node-fetch": "^2.6.0", "ts-node": "^8.10.2", diff --git a/plugins/catalog-graphql/schema.gql b/plugins/catalog-graphql/schema.gql deleted file mode 100644 index 813b2840a6..0000000000 --- a/plugins/catalog-graphql/schema.gql +++ /dev/null @@ -1,62 +0,0 @@ -scalar JSON -scalar JSONObject - -interface EntityMetadata { - name: String! - annotations: JSONObject! - annotation(name: String!): JSON - labels: JSONObject! - label(name: String!): JSON - uid: String! - etag: String! - generation: Int! -} - -type ComponentMetadata implements EntityMetadata { - name: String! - annotations: JSONObject! - annotation(name: String!): JSON - labels: JSONObject! - label(name: String!): JSON - uid: String! - etag: String! - generation: Int! - relationships: String! -} - -type TemplateMetadata implements EntityMetadata { - name: String! - annotations: JSONObject! - annotation(name: String!): JSON - labels: JSONObject! - label(name: String!): JSON - uid: String! - etag: String! - generation: Int! - updatedBy: String! -} - -type DefaultEntityMetadata implements EntityMetadata { - name: String! - annotations: JSONObject! - annotation(name: String!): JSON - labels: JSONObject! - label(name: String!): JSON - uid: String! - etag: String! - generation: Int! -} - -type CatalogEntity { - apiVersion: String! - kind: String! - metadata: EntityMetadata! -} - -type CatalogQuery { - list: [CatalogEntity!]! -} - -type Query { - catalog: CatalogQuery! -} diff --git a/plugins/catalog-graphql/src/graphql/module.ts b/plugins/catalog-graphql/src/graphql/module.ts index f66ff5073e..c2266001b9 100644 --- a/plugins/catalog-graphql/src/graphql/module.ts +++ b/plugins/catalog-graphql/src/graphql/module.ts @@ -15,47 +15,23 @@ */ import { Logger } from 'winston'; -import fs from 'fs'; -import path from 'path'; import { makeExecutableSchema } from 'apollo-server'; import { GraphQLModule } from '@graphql-modules/core'; -import { - Resolvers, - CatalogQuery, - CatalogEntityTypes, - EntityMetadataResolvers, -} from './types'; +import { Resolvers, CatalogQuery } from './types'; import { Config } from '@backstage/config'; import { CatalogClient } from '../service/client'; import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json'; +import { Entity } from '@backstage/catalog-model'; export interface ModuleOptions { logger: Logger; config: Config; } -const schemaPath = path.resolve( - require.resolve('@backstage/plugin-catalog-graphql/package.json'), - '../schema.gql', -); - -const getSpecTypenameForEntity = (e: { kind?: string }) => { - switch (e.kind) { - case 'Component': - return 'ComponentEntity'; - case 'Location': - return 'LocationEntity'; - case 'Template': - return 'TemplateEntity'; - default: - return null; - } -}; - export async function createModule( options: ModuleOptions, ): Promise { - const typeDefs = await fs.promises.readFile(schemaPath, 'utf-8'); + const typeDefs = require('../schema'); const catalogClient = new CatalogClient( options.config.getString('backend.baseUrl'), @@ -64,6 +40,12 @@ export async function createModule( const resolvers: Resolvers = { JSON: GraphQLJSON, JSONObject: GraphQLJSONObject, + DefaultEntitySpec: { + raw: rootValue => { + const { entity } = rootValue as { entity: Entity }; + return entity.spec ?? null; + }, + }, Query: { catalog: () => ({} as CatalogQuery), }, @@ -72,18 +54,15 @@ export async function createModule( return await catalogClient.list(); }, }, - ComponentMetadata: { - relationships: () => 'boop', - }, - TemplateMetadata: { - updatedBy: () => 'blam', - }, CatalogEntity: { - metadata: e => ({ ...e.metadata!, catalogEntity: e }), + metadata: entity => ({ ...entity.metadata!, entity }), + spec: entity => ({ ...entity.spec!, entity }), }, EntityMetadata: { - __resolveType: (obj: any) => { - const kind = obj.catalogEntity!.kind; + __resolveType: rootValue => { + const { + entity: { kind }, + } = rootValue as { entity: Entity }; switch (kind) { case 'Component': return 'ComponentMetadata'; @@ -94,23 +73,35 @@ export async function createModule( } }, annotation: (e, { name }) => e.annotations?.[name] ?? null, - labels: e => e ?? {}, - annotations: e => e ?? {}, + labels: e => e.labels ?? {}, + annotations: e => e.annotations ?? {}, label: (e, { name }) => e.labels?.[name] ?? null, }, + EntitySpec: { + __resolveType: rootValue => { + const { + entity: { kind }, + } = rootValue as { entity: Entity }; + + switch (kind) { + case 'Component': + return 'ComponentEntitySpec'; + case 'Template': + return 'TemplateEntitySpec'; + default: + return 'DefaultEntitySpec'; + } + }, + }, }; const schema = makeExecutableSchema({ typeDefs, resolvers, inheritResolversFromInterfaces: true, - resolverValidationOptions: { - allowResolversNotInSchema: true, - }, }); const module = new GraphQLModule({ - resolverValidationOptions: {}, extraSchemas: [schema], logger: options.logger as any, }); diff --git a/plugins/catalog-graphql/src/graphql/types.ts b/plugins/catalog-graphql/src/graphql/types.ts index e2b10ec6a2..446a1f9f46 100644 --- a/plugins/catalog-graphql/src/graphql/types.ts +++ b/plugins/catalog-graphql/src/graphql/types.ts @@ -13,12 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; +import { + GraphQLResolveInfo, + GraphQLScalarType, + GraphQLScalarTypeConfig, +} from 'graphql'; import { ModuleContext } from '@graphql-modules/core'; export type Maybe = T | null; -export type Exact = { [K in keyof T]: T[K] }; -export type RequireFields = { [X in Exclude]?: T[X] } & { [P in K]-?: NonNullable }; +export type Exact = { + [K in keyof T]: T[K]; +}; +export type Omit = Pick>; +export type RequireFields = { + [X in Exclude]?: T[X]; +} & + { [P in K]-?: NonNullable }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { ID: string; @@ -30,8 +40,6 @@ export type Scalars = { JSONObject: any; }; - - export type EntityMetadata = { name: Scalars['String']; annotations: Scalars['JSONObject']; @@ -43,26 +51,14 @@ export type EntityMetadata = { generation: Scalars['Int']; }; - export type EntityMetadataAnnotationArgs = { name: Scalars['String']; }; - export type EntityMetadataLabelArgs = { name: Scalars['String']; }; -export type ComponentMetadata = EntityMetadata & { - __typename?: 'ComponentMetadata'; - relationships: Scalars['String']; -}; - -export type TemplateMetadata = EntityMetadata & { - __typename?: 'TemplateMetadata'; - updatedBy: Scalars['String']; -}; - export type DefaultEntityMetadata = EntityMetadata & { __typename?: 'DefaultEntityMetadata'; name: Scalars['String']; @@ -73,24 +69,97 @@ export type DefaultEntityMetadata = EntityMetadata & { uid: Scalars['String']; etag: Scalars['String']; generation: Scalars['Int']; - updatedBy: Scalars['String']; }; - export type DefaultEntityMetadataAnnotationArgs = { name: Scalars['String']; }; - export type DefaultEntityMetadataLabelArgs = { name: Scalars['String']; }; +export type ComponentMetadata = EntityMetadata & { + __typename?: 'ComponentMetadata'; + name: Scalars['String']; + annotations: Scalars['JSONObject']; + annotation?: Maybe; + labels: Scalars['JSONObject']; + label?: Maybe; + uid: Scalars['String']; + etag: Scalars['String']; + generation: Scalars['Int']; + relationships?: Maybe; +}; + +export type ComponentMetadataAnnotationArgs = { + name: Scalars['String']; +}; + +export type ComponentMetadataLabelArgs = { + name: Scalars['String']; +}; + +export type TemplateMetadata = EntityMetadata & { + __typename?: 'TemplateMetadata'; + name: Scalars['String']; + annotations: Scalars['JSONObject']; + annotation?: Maybe; + labels: Scalars['JSONObject']; + label?: Maybe; + uid: Scalars['String']; + etag: Scalars['String']; + generation: Scalars['Int']; + updatedBy?: Maybe; +}; + +export type TemplateMetadataAnnotationArgs = { + name: Scalars['String']; +}; + +export type TemplateMetadataLabelArgs = { + name: Scalars['String']; +}; + +export type TemplateEntitySpec = { + __typename?: 'TemplateEntitySpec'; + type: Scalars['String']; + path?: Maybe; + schema: Scalars['JSONObject']; + templater: Scalars['String']; +}; + +export type ComponentEntitySpec = { + __typename?: 'ComponentEntitySpec'; + title: Scalars['String']; + lifecycle: Scalars['String']; + owner: Scalars['String']; +}; + +export type LocationEntitySpec = { + __typename?: 'LocationEntitySpec'; + type: Scalars['String']; + target?: Maybe; + targets: Array; +}; + +export type DefaultEntitySpec = { + __typename?: 'DefaultEntitySpec'; + raw?: Maybe; +}; + +export type EntitySpec = + | DefaultEntitySpec + | TemplateEntitySpec + | LocationEntitySpec + | ComponentEntitySpec; + export type CatalogEntity = { __typename?: 'CatalogEntity'; apiVersion: Scalars['String']; kind: Scalars['String']; - metadata: EntityMetadata; + metadata?: Maybe; + spec: EntitySpec; }; export type CatalogQuery = { @@ -108,7 +177,6 @@ export type ResolversObject = WithIndex; export type ResolverTypeWrapper = Promise | T; - export type LegacyStitchingResolver = { fragment: string; resolve: ResolverFn; @@ -118,7 +186,9 @@ export type NewStitchingResolver = { selectionSet: string; resolve: ResolverFn; }; -export type StitchingResolver = LegacyStitchingResolver | NewStitchingResolver; +export type StitchingResolver = + | LegacyStitchingResolver + | NewStitchingResolver; export type Resolver = | ResolverFn | StitchingResolver; @@ -127,26 +197,42 @@ export type ResolverFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => Promise | TResult; export type SubscriptionSubscribeFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => AsyncIterator | Promise>; export type SubscriptionResolveFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => TResult | Promise; -export interface SubscriptionSubscriberObject { - subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; - resolve?: SubscriptionResolveFn; +export interface SubscriptionSubscriberObject< + TResult, + TKey extends string, + TParent, + TContext, + TArgs +> { + subscribe: SubscriptionSubscribeFn< + { [key in TKey]: TResult }, + TParent, + TContext, + TArgs + >; + resolve?: SubscriptionResolveFn< + TResult, + { [key in TKey]: TResult }, + TContext, + TArgs + >; } export interface SubscriptionResolverObject { @@ -154,43 +240,82 @@ export interface SubscriptionResolverObject { resolve: SubscriptionResolveFn; } -export type SubscriptionObject = +export type SubscriptionObject< + TResult, + TKey extends string, + TParent, + TContext, + TArgs +> = | SubscriptionSubscriberObject | SubscriptionResolverObject; -export type SubscriptionResolver = - | ((...args: any[]) => SubscriptionObject) +export type SubscriptionResolver< + TResult, + TKey extends string, + TParent = {}, + TContext = {}, + TArgs = {} +> = + | (( + ...args: any[] + ) => SubscriptionObject) | SubscriptionObject; export type TypeResolveFn = ( parent: TParent, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => Maybe | Promise>; -export type IsTypeOfResolverFn = (obj: T, info: GraphQLResolveInfo) => boolean | Promise; +export type IsTypeOfResolverFn = ( + obj: T, + info: GraphQLResolveInfo, +) => boolean | Promise; export type NextResolverFn = () => Promise; -export type DirectiveResolverFn = ( +export type DirectiveResolverFn< + TResult = {}, + TParent = {}, + TContext = {}, + TArgs = {} +> = ( next: NextResolverFn, parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => TResult | Promise; /** Mapping between all available schema types and the resolvers types */ export type ResolversTypes = ResolversObject<{ JSON: ResolverTypeWrapper>; JSONObject: ResolverTypeWrapper>; - EntityMetadata: ResolversTypes['ComponentMetadata'] | ResolversTypes['TemplateMetadata'] | ResolversTypes['DefaultEntityMetadata']; + EntityMetadata: + | ResolversTypes['DefaultEntityMetadata'] + | ResolversTypes['ComponentMetadata'] + | ResolversTypes['TemplateMetadata']; String: ResolverTypeWrapper>; Int: ResolverTypeWrapper>; + DefaultEntityMetadata: ResolverTypeWrapper>; ComponentMetadata: ResolverTypeWrapper>; TemplateMetadata: ResolverTypeWrapper>; - DefaultEntityMetadata: ResolverTypeWrapper>; - CatalogEntity: ResolverTypeWrapper>; + TemplateEntitySpec: ResolverTypeWrapper>; + ComponentEntitySpec: ResolverTypeWrapper>; + LocationEntitySpec: ResolverTypeWrapper>; + DefaultEntitySpec: ResolverTypeWrapper>; + EntitySpec: Partial< + | ResolversTypes['DefaultEntitySpec'] + | ResolversTypes['TemplateEntitySpec'] + | ResolversTypes['LocationEntitySpec'] + | ResolversTypes['ComponentEntitySpec'] + >; + CatalogEntity: ResolverTypeWrapper< + Partial< + Omit & { spec: ResolversTypes['EntitySpec'] } + > + >; CatalogQuery: ResolverTypeWrapper>; Query: ResolverTypeWrapper<{}>; Boolean: ResolverTypeWrapper>; @@ -200,74 +325,241 @@ export type ResolversTypes = ResolversObject<{ export type ResolversParentTypes = ResolversObject<{ JSON: Partial; JSONObject: Partial; - EntityMetadata: ResolversParentTypes['ComponentMetadata'] | ResolversParentTypes['TemplateMetadata'] | ResolversParentTypes['DefaultEntityMetadata']; + EntityMetadata: + | ResolversParentTypes['DefaultEntityMetadata'] + | ResolversParentTypes['ComponentMetadata'] + | ResolversParentTypes['TemplateMetadata']; String: Partial; Int: Partial; + DefaultEntityMetadata: Partial; ComponentMetadata: Partial; TemplateMetadata: Partial; - DefaultEntityMetadata: Partial; - CatalogEntity: Partial; + TemplateEntitySpec: Partial; + ComponentEntitySpec: Partial; + LocationEntitySpec: Partial; + DefaultEntitySpec: Partial; + EntitySpec: Partial< + | ResolversParentTypes['DefaultEntitySpec'] + | ResolversParentTypes['TemplateEntitySpec'] + | ResolversParentTypes['LocationEntitySpec'] + | ResolversParentTypes['ComponentEntitySpec'] + >; + CatalogEntity: Partial< + Omit & { spec: ResolversParentTypes['EntitySpec'] } + >; CatalogQuery: Partial; Query: {}; Boolean: Partial; }>; -export interface JsonScalarConfig extends GraphQLScalarTypeConfig { +export interface JsonScalarConfig + extends GraphQLScalarTypeConfig { name: 'JSON'; } -export interface JsonObjectScalarConfig extends GraphQLScalarTypeConfig { +export interface JsonObjectScalarConfig + extends GraphQLScalarTypeConfig { name: 'JSONObject'; } -export type EntityMetadataResolvers = ResolversObject<{ - __resolveType: TypeResolveFn<'ComponentMetadata' | 'TemplateMetadata' | 'DefaultEntityMetadata', ParentType, ContextType>; +export type EntityMetadataResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['EntityMetadata'] +> = ResolversObject<{ + __resolveType: TypeResolveFn< + 'DefaultEntityMetadata' | 'ComponentMetadata' | 'TemplateMetadata', + ParentType, + ContextType + >; name?: Resolver; annotations?: Resolver; - annotation?: Resolver, ParentType, ContextType, RequireFields>; + annotation?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; labels?: Resolver; - label?: Resolver, ParentType, ContextType, RequireFields>; + label?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; uid?: Resolver; etag?: Resolver; generation?: Resolver; }>; -export type ComponentMetadataResolvers = ResolversObject<{ - relationships?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; - -export type TemplateMetadataResolvers = ResolversObject<{ - updatedBy?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; - -export type DefaultEntityMetadataResolvers = ResolversObject<{ +export type DefaultEntityMetadataResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['DefaultEntityMetadata'] +> = ResolversObject<{ name?: Resolver; annotations?: Resolver; - annotation?: Resolver, ParentType, ContextType, RequireFields>; + annotation?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; labels?: Resolver; - label?: Resolver, ParentType, ContextType, RequireFields>; + label?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; uid?: Resolver; etag?: Resolver; generation?: Resolver; - updatedBy?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type CatalogEntityResolvers = ResolversObject<{ +export type ComponentMetadataResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['ComponentMetadata'] +> = ResolversObject<{ + name?: Resolver; + annotations?: Resolver; + annotation?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; + labels?: Resolver; + label?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; + uid?: Resolver; + etag?: Resolver; + generation?: Resolver; + relationships?: Resolver< + Maybe, + ParentType, + ContextType + >; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type TemplateMetadataResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['TemplateMetadata'] +> = ResolversObject<{ + name?: Resolver; + annotations?: Resolver; + annotation?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; + labels?: Resolver; + label?: Resolver< + Maybe, + ParentType, + ContextType, + RequireFields + >; + uid?: Resolver; + etag?: Resolver; + generation?: Resolver; + updatedBy?: Resolver< + Maybe, + ParentType, + ContextType + >; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type TemplateEntitySpecResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['TemplateEntitySpec'] +> = ResolversObject<{ + type?: Resolver; + path?: Resolver, ParentType, ContextType>; + schema?: Resolver; + templater?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type ComponentEntitySpecResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['ComponentEntitySpec'] +> = ResolversObject<{ + title?: Resolver; + lifecycle?: Resolver; + owner?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type LocationEntitySpecResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['LocationEntitySpec'] +> = ResolversObject<{ + type?: Resolver; + target?: Resolver, ParentType, ContextType>; + targets?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DefaultEntitySpecResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['DefaultEntitySpec'] +> = ResolversObject<{ + raw?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type EntitySpecResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['EntitySpec'] +> = ResolversObject<{ + __resolveType: TypeResolveFn< + | 'DefaultEntitySpec' + | 'TemplateEntitySpec' + | 'LocationEntitySpec' + | 'ComponentEntitySpec', + ParentType, + ContextType + >; +}>; + +export type CatalogEntityResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['CatalogEntity'] +> = ResolversObject<{ apiVersion?: Resolver; kind?: Resolver; - metadata?: Resolver; + metadata?: Resolver< + Maybe, + ParentType, + ContextType + >; + spec?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type CatalogQueryResolvers = ResolversObject<{ - list?: Resolver, ParentType, ContextType>; +export type CatalogQueryResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['CatalogQuery'] +> = ResolversObject<{ + list?: Resolver< + Array, + ParentType, + ContextType + >; __isTypeOf?: IsTypeOfResolverFn; }>; -export type QueryResolvers = ResolversObject<{ +export type QueryResolvers< + ContextType = ModuleContext, + ParentType = ResolversParentTypes['Query'] +> = ResolversObject<{ catalog?: Resolver; }>; @@ -275,15 +567,19 @@ export type Resolvers = ResolversObject<{ JSON?: GraphQLScalarType; JSONObject?: GraphQLScalarType; EntityMetadata?: EntityMetadataResolvers; + DefaultEntityMetadata?: DefaultEntityMetadataResolvers; ComponentMetadata?: ComponentMetadataResolvers; TemplateMetadata?: TemplateMetadataResolvers; - DefaultEntityMetadata?: DefaultEntityMetadataResolvers; + TemplateEntitySpec?: TemplateEntitySpecResolvers; + ComponentEntitySpec?: ComponentEntitySpecResolvers; + LocationEntitySpec?: LocationEntitySpecResolvers; + DefaultEntitySpec?: DefaultEntitySpecResolvers; + EntitySpec?: EntitySpecResolvers; CatalogEntity?: CatalogEntityResolvers; CatalogQuery?: CatalogQueryResolvers; Query?: QueryResolvers; }>; - /** * @deprecated * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. From ce2ed89e57c8e978a62fbb77f423ad1bcde79ace Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 12 Aug 2020 01:47:09 +0200 Subject: [PATCH 030/219] feat(gql/catalog): to remove the duplication in the schema thanks to the limitations on implements, let's create the schema using javascript Trust me, i've tried getting this to work with a typescript file, but it just would not work with the import export keywords which are needed to satisfy typescript config. couldn't get it to transpile anything properly. so thought screw it for now --- packages/backend-common/src/gql.ts | 17 ++++++ packages/backend-common/src/index.ts | 1 + plugins/catalog-graphql/src/schema.js | 87 +++++++++++++++++++++++++++ yarn.lock | 65 +++++++++++++++++++- 4 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 packages/backend-common/src/gql.ts create mode 100644 plugins/catalog-graphql/src/schema.js diff --git a/packages/backend-common/src/gql.ts b/packages/backend-common/src/gql.ts new file mode 100644 index 0000000000..ca6b9c7f55 --- /dev/null +++ b/packages/backend-common/src/gql.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const gql = (s: TemplateStringsArray, ...args: any[]) => + s.map((ss, i) => `${ss}${args[i] || ''}`).join(''); diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index a3f57ed9df..4b9518c6bc 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -20,3 +20,4 @@ export * from './logging'; export * from './middleware'; export * from './service'; export * from './hot'; +export * from './gql'; diff --git a/plugins/catalog-graphql/src/schema.js b/plugins/catalog-graphql/src/schema.js new file mode 100644 index 0000000000..473aad97f5 --- /dev/null +++ b/plugins/catalog-graphql/src/schema.js @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const EntityMetadataFields = /* GraphQL */ ` + name: String! + annotations: JSONObject! + annotation(name: String!): JSON + labels: JSONObject! + label(name: String!): JSON + uid: String! + etag: String! + generation: Int! +`; + +const EntityMetadata = /* GraphQL */ ` + scalar JSON + scalar JSONObject + + interface EntityMetadata { + ${EntityMetadataFields} + } + + type DefaultEntityMetadata implements EntityMetadata { + ${EntityMetadataFields} + } + + type ComponentMetadata implements EntityMetadata { + ${EntityMetadataFields} + # mock field to prove extensions working + relationships: String + } + + type TemplateMetadata implements EntityMetadata { + ${EntityMetadataFields} + # mock field to prove extensions working + updatedBy: String + } + + type TemplateEntitySpec { + type: String! + path: String + schema: JSONObject! + templater: String! + } + + type ComponentEntitySpec { + type: String! + lifecycle: String! + owner: String! + } + + type DefaultEntitySpec { + raw: JSONObject + } + + union EntitySpec = DefaultEntitySpec | TemplateEntitySpec | ComponentEntitySpec + + type CatalogEntity { + apiVersion: String! + kind: String! + metadata: EntityMetadata + spec: EntitySpec! + } + + type CatalogQuery { + list: [CatalogEntity!]! + } + + type Query { + catalog: CatalogQuery! + } +`; + +module.exports = EntityMetadata; diff --git a/yarn.lock b/yarn.lock index 7dfa9e500f..b4c5a4fe49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4460,7 +4460,7 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express@*", "@types/express@^4.17.6", "@types/express@^4.17.7": +"@types/express@*", "@types/express@4.17.7", "@types/express@^4.17.6", "@types/express@^4.17.7": version "4.17.7" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59" integrity sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ== @@ -5843,6 +5843,34 @@ apollo-server-core@^2.16.0: subscriptions-transport-ws "^0.9.11" ws "^6.0.0" +apollo-server-core@^2.16.1: + version "2.16.1" + resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.16.1.tgz#5b5b8245ab9c0cb6c2367ec19ab855dea6ccea3a" + integrity sha512-nuwn5ZBbmzPwDetb3FgiFFJlNK7ZBFg8kis/raymrjd3eBGdNcOyMTJDl6J9673X9Xqp+dXQmFYDW/G3G8S1YA== + 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" @@ -5878,6 +5906,28 @@ apollo-server-express@^2.16.0: subscriptions-transport-ws "^0.9.16" type-is "^1.6.16" +apollo-server-express@^2.16.1: + version "2.16.1" + resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.16.1.tgz#7438bca590ef8577d24d20ba0b6d582c120c0146" + integrity sha512-Oq5YNcaMYnRk6jDmA9LWf8oSd2KHDVe7jQ4wtooAvG9FVUD+FaFBgSkytXHMvtifQh2wdF07Ri8uDLMz6IQjTw== + 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.7" + accepts "^1.3.5" + apollo-server-core "^2.16.1" + 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" @@ -5905,6 +5955,17 @@ apollo-server@^2.16.0: graphql-subscriptions "^1.0.0" graphql-tools "^4.0.0" +apollo-server@^2.16.1: + version "2.16.1" + resolved "https://registry.npmjs.org/apollo-server/-/apollo-server-2.16.1.tgz#edc319606eb29f73132239bdc005dd88ac40a142" + integrity sha512-oy9NVRzGwlpQ+W1DwLKRH+KASmodSYpvYIRY5DMAZtGqNmT2zOCpbIZVjBt23SuPB5NhIhhE4ROzoObRv3zy5w== + dependencies: + apollo-server-core "^2.16.1" + apollo-server-express "^2.16.1" + 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" @@ -11340,7 +11401,7 @@ graphql-subscriptions@^1.0.0: dependencies: iterall "^1.2.1" -graphql-tag@2.11.0: +graphql-tag@2.11.0, graphql-tag@^2.11.0: version "2.11.0" resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz#1deb53a01c46a7eb401d6cb59dec86fa1cccbffd" integrity sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA== From 3600b0216fc27b6c730001f99cf231c34cbd4014 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 12 Aug 2020 02:10:46 +0200 Subject: [PATCH 031/219] chore: reverting code --- packages/backend-common/src/gql.ts | 17 ----------------- packages/backend-common/src/index.ts | 1 - 2 files changed, 18 deletions(-) delete mode 100644 packages/backend-common/src/gql.ts diff --git a/packages/backend-common/src/gql.ts b/packages/backend-common/src/gql.ts deleted file mode 100644 index ca6b9c7f55..0000000000 --- a/packages/backend-common/src/gql.ts +++ /dev/null @@ -1,17 +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 const gql = (s: TemplateStringsArray, ...args: any[]) => - s.map((ss, i) => `${ss}${args[i] || ''}`).join(''); diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 4b9518c6bc..a3f57ed9df 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -20,4 +20,3 @@ export * from './logging'; export * from './middleware'; export * from './service'; export * from './hot'; -export * from './gql'; From 56d93dcb52105642d0398491331d79f6a8898710 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 14 Aug 2020 13:57:40 +0200 Subject: [PATCH 032/219] chore(gql/catalog): types as ts --- plugins/catalog-graphql/codegen.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-graphql/codegen.yml b/plugins/catalog-graphql/codegen.yml index 4a5795eb9e..e100277942 100644 --- a/plugins/catalog-graphql/codegen.yml +++ b/plugins/catalog-graphql/codegen.yml @@ -1,6 +1,6 @@ overwrite: true generates: - ./src/graphql/types.d.ts: + ./src/graphql/types.ts: schema: ./src/schema.js plugins: - typescript From a09e949b6f9ff9d8efee1a24b37d7a7fc1a29af5 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 14 Aug 2020 14:00:39 +0200 Subject: [PATCH 033/219] chore(gql/catalog): fixing dependencies --- plugins/catalog-graphql/package.json | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 83e88e1d25..79277bdc2b 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -6,7 +6,7 @@ "license": "Apache-2.0", "publishConfig": { "access": "public", - "main": "dist/index.esm.js", + "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, "scripts": { @@ -20,26 +20,25 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.18", "@backstage/catalog-model": "^0.1.1-alpha.18", + "@backstage/backend-common": "^0.1.1-alpha.18", "@backstage/config": "^0.1.1-alpha.18", - "@graphql-codegen/cli": "^1.17.7", - "@graphql-codegen/typescript": "^1.17.7", - "@graphql-codegen/typescript-resolvers": "^1.17.7", "@graphql-modules/core": "^0.7.17", "apollo-server": "^2.16.1", "graphql": "^15.3.0", "graphql-tag": "^2.11.0", "graphql-type-json": "^0.3.2", "node-fetch": "^2.6.0", - "ts-node": "^8.10.2", - "winston": "^3.2.1", - "yn": "^4.0.0" + "winston": "^3.2.1" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.18", "@types/express": "^4.17.7", "@types/supertest": "^2.0.8", + "@graphql-codegen/cli": "^1.17.7", + "@graphql-codegen/typescript": "^1.17.7", + "@graphql-codegen/typescript-resolvers": "^1.17.7", + "ts-node": "^8.10.2", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.19.5", "supertest": "^4.0.2" From 9b3236f47edba427ca35a3cef48586bdf0467694 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 14 Aug 2020 14:37:17 +0200 Subject: [PATCH 034/219] chore(gql/catalog): fixing some code review comments --- plugins/catalog-graphql/package.json | 3 +- plugins/graphql/package.json | 4 +- plugins/graphql/src/service/router.ts | 17 ++++--- yarn.lock | 71 --------------------------- 4 files changed, 13 insertions(+), 82 deletions(-) diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 79277bdc2b..5531c23ade 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -44,7 +44,6 @@ "supertest": "^4.0.2" }, "files": [ - "dist", - "schema.gql" + "dist" ] } diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 6f8ff50c1a..35aac33451 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -24,8 +24,8 @@ "@backstage/plugin-catalog-graphql": "^0.1.1-alpha.18", "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", - "apollo-server": "^2.16.0", - "apollo-server-express": "^2.16.0", + "apollo-server": "^2.16.1", + "apollo-server-express": "^2.16.1", "express": "^4.17.1", "express-promise-router": "^3.0.3", "graphql": "^15.3.0", diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts index cd926da3a2..a06f696533 100644 --- a/plugins/graphql/src/service/router.ts +++ b/plugins/graphql/src/service/router.ts @@ -51,19 +51,22 @@ export async function createRouter( const server = new ApolloServer({ schema, logger: options.logger, + introspection: true, + playground: process.env.NODE_ENV === 'development', }); const router = Router(); const apolloMiddlware = server.getMiddleware({ path: '/' }); - router.use( - helmet.contentSecurityPolicy({ - directives: { - defaultSrc: ["'self'", "'unsafe-inline'", 'http://*'], - }, - }), - ); + if (process.env.NODE_ENV === 'development') + router.use( + helmet.contentSecurityPolicy({ + directives: { + defaultSrc: ["'self'", "'unsafe-inline'", 'http://*'], + }, + }), + ); router.use(apolloMiddlware); diff --git a/yarn.lock b/yarn.lock index dec603d9db..e15dd3bed6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4703,16 +4703,6 @@ "@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" @@ -6091,34 +6081,6 @@ apollo-server-caching@^0.5.2: 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-core@^2.16.1: version "2.16.1" resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.16.1.tgz#5b5b8245ab9c0cb6c2367ec19ab855dea6ccea3a" @@ -6160,28 +6122,6 @@ apollo-server-errors@^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-express@^2.16.1: version "2.16.1" resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.16.1.tgz#7438bca590ef8577d24d20ba0b6d582c120c0146" @@ -6220,17 +6160,6 @@ apollo-server-types@^0.5.1: 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-server@^2.16.1: version "2.16.1" resolved "https://registry.npmjs.org/apollo-server/-/apollo-server-2.16.1.tgz#edc319606eb29f73132239bdc005dd88ac40a142" From e4d5628cf4fded262ab1e94860ae98f54e46d0ca Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 14 Aug 2020 14:50:01 +0200 Subject: [PATCH 035/219] docs(catalog/grqphql): updating docs --- plugins/catalog-graphql/README.md | 14 ++++++-------- plugins/graphql/README.md | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/plugins/catalog-graphql/README.md b/plugins/catalog-graphql/README.md index 3f85ec766e..cec3204c52 100644 --- a/plugins/catalog-graphql/README.md +++ b/plugins/catalog-graphql/README.md @@ -1,13 +1,11 @@ -# graphql +# Catalog GraphQL Plugin -Welcome to the graphql backend plugin! +## Getting Started -_This plugin was created through the Backstage CLI_ +This is the Catalog GraphQL plugin. -## Getting started +It provides the `catalog` part of the GraphQL schema. -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). +To register it with the GraphQL backend, be sure to follow the [Gettting Started](../graphql/README.md#getting-started) guide of the GraphQL plugin. -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. +g%g%+DGr%*$+wR8UWGak zZY+2kLB1f1M9l79P5KD1+m~e`%g=RcN*g=zXSt`qblQeOzL6g(2Amk?HP4uRkQm{u zFAsi>naOA!mhzVe17ITq@X+JvH&Svw32@{O#BTe* zv%5Od^CDM*(WFq$@K?u4WoK#aft=k?Wik^qA?@I+oF8E{VG866$TL>{rMtW&Z|8(@ zdv159$M&;vsYx%WC0pnaCW1WW9l_)3ZO5U)tGji8wB7H+CP{sEPv((a} z8F5CzupkC5!Bn$`Uou0|^j`bV@egPM_L&wf6F~VRLqR*%q9>yVsKja~v+vDyQyi%$P;qn8bXl>D($^L$_kob>Dl)B>(O^s_AmYS3jW$)6bAcs` zm4{edRQ=8JviI%grEx9avF6OJuKI`NWIuA|E5Y=$@5a5XSM3ZRSds2^vYhe~*_y?s@G>6h=g4c)89bN{-b5wY06vERH{coIOd;q(DWK z&&+wZZ{eO@UZr(Ib1nK4*uks3ptBd3OY!me!HDd!@|tv{!NV~w-*IiF&4~ZWp$8xd z18CeyCXR5;yndz$r|3;`Icv*rVw;vm&+3?uhN%(l7PJI{jw5&L(Yhy}W+--@X#URH zrcwsb_>xR`(fH~*pQ}h8XY5%8nV8QQ_~;65+e#Gq?lnE0ty(&JRkXjC7!wDkS1>Zq znB<%8%qN{Vkk0noe#8~i)!twKpF}R=-F1!dQ{m}PH?sSpKymV9*8eKA)(!%y84BAb zwf^n}keVI_a=2svJ?-D(@c-`&(#oxE_O{yj>TJ#r?0B=Rz(X3RL>$BCS4_IA(ECZ1 zf6GI|Tf_Ysqy}|rZ-18`TDhfO80%%}3vGs}dZbUO*eGGUDH|2B3Ta zxHLRi>Bg0{WY+~O1pR_+AGt}AC6+_$CM_NJ_RU1x3u7yQrEXw{=17nM6Hb^tzAxRF2|CsN3W#h`^=rqJ_x6Sr@P&J z&_ebZ;wvT<#+rI=U^xxuy|bJ3U1rRVhZxF(Vi@FP{}h%|1vH3Z$xXla^S~e5x>?@C z11yrQ=bf(qETz_4wAT+}$YG7WyJ3hjLf}}#XTCrG3<;%Ky9GjcYfrkMaCl zZ+e{%kGfu)tR}@b>**SRW7UKY;5V9ZWCglHN1#*q^hRmWf4lcz>;1p;y-P_CK2>k6 To$z!F;Gcqw>Wh-+CV~G4?FVZf diff --git a/plugins/jenkins/src/assets/folder-results.png b/plugins/jenkins/src/assets/folder-results.png index 2e14f3551f6cbfbd58356051ad28584c5510b9c7..c01e4cf43752019a5edd2d6e18edeec134752a19 100644 GIT binary patch literal 81758 zcmeGERa9Nu(gg}*ArLG;0t9z=2(oYq1b24`0fM_*a8K|63%B3|cZZ-0cXxuj!=3EC z&punu|M0)ub{{@kTaC%8VKu61NUuH#k(U)md4c-^3JMBE;=QOM6cp?(6cmgF0zB|d zKszfK3hKqMnTUwIgop^Syq)z&GYexVsP`dp3D1>dda-)8ZbwCgC7_^bGA=4tEAd<)8V0;eM(a>(IN+*Ts`^@<6l{OM_VQ{da)+;*NdLbHjN zBPE3+=GcAwba)7D+P74lQgD9Q@~D<&Q)|cpC6CcggU`Z)w1WD9*BI#?H&i+*iS7Lc zcRNjUc-apGvzIFmG_uJnkz+~4!b9~!g^bzFn7t>6W@_hv zLDlnNJ|S=^bEQXZr$Vp(*{8|TrRXp7#e)Ru18@6TtiXiytZAHk`n;3-Z$CiX1KS##q4${|H7e;9t5pkr2C?+hkIdIchvR6Z^ceU3 z+B(i$`^cR@gvZgp8lJyghfk!TO&$_^4tWjd0x8~Qnsbl8lDpktGq$&P8S(wmRIz!p z^#aHKZP)RSMJ{c4t~!Mub=*HUQj+mVcsd+ZflH!Q;V$%3a(Wc)&j~R=<9MrojUxecf&KNljyocnrBb}3Q(YiX$*w}X^UUn(8!~4F!J!f1o5_E zG4(^vHsR)du#aGci19`J;L702pwT`+qpPE;`k|ylz49f{N27*~TPDtj6>qa0h8glX zUB;+|xBA?$jQ-^_$q`1l@1;J>XP|Ci{ufX->>J_DF9f1Nyum!!VIc$RSEJoj09f{*}u>okn$8W{dg(U^v zq{G#QzY&Eopm2rEhRuVp8Gb1s=Sa#0|CziUwMr~wAI3ZJ>-2VokbLD>geokRU|J!S zEL;W4e5-NG*jH~uZ~VJ6znkLDV(bT^iuR<>Y+IaZo~b&#sYhK1KM-CLLrd=&USj?b zPT7sW7HVL)%3_wdkq9G=m@KNyj$JGf|vt3IZ`KJA#@?ivfWAF zz1(lw79TY)lC7t1t=*Ph8?^+g2F)4H8Pyqy)BiR&p<{P>|LEaLv=OQWUGK9%xOLA` zJK7o*LMWWbXF8IUR+1ue3o3llN_4B2fe4tQ6qzg|U>q4~Dl6i647?ERko(RWJ3Bi! zJ4QRCjVrs|4atqjUY&TnFSnwZUk2XuXOl2IBY4PetuQ z^%57&`43%D08f_XWv%*n>96Ct|4Ob|7S5$k(x)Q1)qdfd~>H_|WpuSwIOu|gv zY*ZSfxQMuKeLH>c`o!bhV|$dTX;Ep*Y3Y>vRE7%Pt0)wR6-ZA=PwW@;s2qOxO3iy4 z#S+D1-QU>lTC?dXpIo|`)2%|MY_8Z)c%cE#J;+PVXjW=^cV7;#*&%I|(QOw2i;MkM zY>N)!oD50T<9fWh3rZ@>{q`|?lXlQAY3glHRb>@SrN8EFC8KG?r@~L}W_jj^lU7rN zBN@M3wvQa0c2mY&g_JT_OutnsYGPYTt0{5Ht7U5C%w_Rpm=s}W)hLuo9G3Gdq?M!< zv}m;`c@GHo5RdCu3Hh~&hIn1&3Kkl3B)>`-MyY-7Igy5#3>cv|7SZ7#A5p^+jmku0; zw4{0`dHX%8`xvzr_(Tdy>)R|3wcbiIP$p|Y@R96LH8ICuOTUPG!HDFAYlubq!VD=u z#822c;3DWvAY!{_yAT(fhuYN|!dmcNupX8nwirFV-WB~Qhn?dG=T~n(nT@aHUydw} z%w-XEC3e0y1R1?Hj4|9b66n0{e%&LnR=hUVsV6o3ewnJ0auve}(;TA*`9hRO(W;wZi?@Z1MDGez(a--FqEoJDt9!g$ zGORP$VAg=-+%i|;0Exp0itcObY>I9g9sM#o=?Q=9w0Eo6P1m4xptGo)p;4Z*+Tk_y zp!K{Fm0k6d3YLo3!`DY6JrzL}`zi&;UZK6Vc|Qnh7vZmmkOw8t2r(Ij!6{9l;!4IJ zC54N1%@1XpEBh!me%S`=??=BTi+e^@b7*p?zWvVengF(kyhrygwO;-3;5NVW%>BtXx)L>6y&OHux=n}T z>-x2Gr54bGTS3qFyz9Wzh{}!+3t%aev$L>Ec4jhn~jf&`|Y(Qw1jy- zvc^7!-xYmOH>rK7TR3GSdm!V~qjzIDQ@pY>tXw9Ne9U(-KKZpyr0cCs6h?&S#^Aeu9wRPLl4OImt zRRBG@x#o-XFVK0pFh{vqBb|s)^46qw#v|dmsT`)8-mUDukDYAw?z1US`9kxt^Pwj& z%Yx6Lx_^EV?~Z*XoQ-Z_&4l;JYFp5>Ak7dBPbveVq*{`O@sWaGHixXQi|(wiPhn)w zI76i%C6Ama1@ifh^Ml@bestSyTe1+_dNhmjz8nK{`Lx`6!_b(&eo517Zw4H3RgKjp zKFY{I(E`s1P|!hUP;kH#H1NR%KEP=>IuPnP@EZg8h^E8*M+&=}4*Q>H7>lQZLP{bM z62NaIBRgYbD|=IGhh4fGC!nb*Gi7xLbs1@pk+mhG!3S$YV@4NCo2M#Jye=T%(bCw# zfY`;-!pa`x!bkdB2@vr7^fePH@oz;O%=t*wW#oxPtnG}6-!if@GL!PZASNc}wfpc9 zq$n!(M|I#YK2lQ$2OAI*le4okqca<$wVeqQ3pY166EiClD=Py~g2CR^%E7>e!OEWO z_eTEHj;OJ{k)4^1gPFAz@l(47hSrV_e59mL9sT+9`#6nV%>LVxmHi)T0SYobNtjp| znVJ4<8>q_r^esr<%*EJ3UDV7Hm>!@HeimkCHs0SV{7dxTE`P78VsC6GVr>c3bm0H* z1piU_ufl&;{H;%o|Mtnj%KXVKJv-{<+aZ-I&Cf5FT2r_uOdq-0Ni zfr1i*k`NVAc7fglKW`*>HxoqFbO{$N50i0;fv(_&X@&)tgPK%46<@I3l{zr#0R6Z003lR@uK`}oVx7;-q8`H(32tyry{*QTiOX*`O9<&vE?%MWa z=mpm5jaBr2=j=bDNjFAF@2GeWaW5j9)KCZI^O>9}{cUo8E9kpPB`+I<-3g*?R_^8W zeoe3NKguY=CES5j*@{UJsMW=Z0(@l@93q?fAW>UeGJQnetdA?{yMTKU3P z6>~TElq`~qw5Yrr7lrVuu(TQ1>+yDp>UdFKOil8?&i%*Q_(@RzbdVD*itj=lMY5#j zCzi9{;X;Em*Fo)CyS3M8vRD9ULTn%=!E$<%{NHgv1A}nxc`=ihlB_{3eLk3KomjPaN4sy)D*0%IXI8CX1du@hI_$ZaT^%yy;Xh+@z+i8QKfSs|t zshdXi=Kt0a1 zU(BrxaxPi0d%F*ojmh#nd+3zoJ6FCs^nRSvMKk(Ve0j9e(IH7!9E*zy;k(o>Bs3{5 zsoDhP`@%lYbszuy3`x_2SJ9rpZ{x`h>bi(8kwQpfxYcbp1(S~xTJDT1f}J|Av9v6{ zHu9qHaYwviqvX9>_A|KHn{ibr^gL)h9r{jK36{b5A~=RHPBlK_T-tP@S^M~K+p*po zlUyAWT}&mLf&+5eOFW~wOhG$jr+AZmxFe_8zk+AC9B)5ybH~i<{<#Ie6UP&i&6qgO|w3D3;0lx__k$TMs#i$jhZ`Zi3Y4X>tm7yYZ~?z3$eC@**RE4Zoo-v%6 zIPK$F)osM%5`FKIO5Z>nW(hzwAGoURqb(#S+qkuLpL2u6i*V@jF}2+@sqUh;JQ z<}{VZ_BkwpFdcB6{HwF8rf-Nv4F~l*0t1f)xaErj6J=b#Z%Bg4WJlhQm);WjM4o3O zj&R)jQplnL_d9Z9Y_aDu5{1RpnzWDSQ+je;17IFbX)x%mtKzD{OqO^Qv-xCE?q0hk z#qcHqu_O=z8{^z6OJx{_p(t-y$tOJbrpql4aN?_lxli0}_3MZpj)ElejYM;^P}}(J z)_>Xzac|M@Tj9=^Y#M@LTNQOvUb@K0#I_M-H4Dh(Y!JCD`^J4Nrd^`ROHovLxJdj) zXpX_xi=4sy=YhvJp8U()H}&!UD#UWP6_t>$LDgjN895TY1aCEQ%F`1l17Vs0@9R}! zE5&2h7Yr7W=~!;_i}{L>Fte5B&0mVDQ|}-_nCLQOkRiU#Mt%x7lC0~?$5^2t*p8n+ zMcl(sq3Q1`i&;2i5}8SOZ??0fkT?cydB$Flu(=yT`;HSU$@E}3`%hP52KRZ2NI zWN(YlG5R!~cgHu!cut>uSV+}lFf72$kye&%wr{V{irh#b@4iDOr)* z4YL*hxblLo=u2S5GO0S_AqHO0GmE)Wy%q=Ql&C%a2^_q>zDI6cZtc=Lwc3wn_z{GB zpn1UD(EZ|OtJ)QM4@l#u7jxE3gR9oRg^%fTI8(W=E~rxpraZ)xuBmEsUO$aT#j={j z@uHnzjY&Ui46)_c?!QBR6{vUOAw+&!lri|efh6p>xO$ehio~pD>smVz2G!Q9!njz#q!x_Yn=_k8&e>b9BI_>aI=e~o5|bY%Kw9D79GKPmafWSVld@S zn*;HKuP*I$Sb*D51w95e)ik%P=&kD(7`yp?-#U+jV_)4t)IK*U%+X)WRuea4=pvY| zvwe&IvQF6671`ZD=o{10&14NsQ#|)^@)`KhgDNDS=zd!~fMqw>5$^e#>Jp&+c#KA4 z3oI%ZG5Dobb`PPu1qMys{Qc#Uz*;UhWM-Yrd!|&5=z8w#ZU>{I!IZt1hP8 zCFX4B6#uKO#C*AjG~%Hsfrl%Ki!SfLMhBmDWeHsn+caaIZj)Qi62ji6PPaT+a5SF& z&RY?t2MH>$axiwAm(#p$kB^jr?Gb+bnElUT%pz>=5u9b*&pNiGA7N2~DuzIXF2@*4 zPm5{LlUCnXIubAF$LS1246X%w&ifp@>gXRzZ5DB0Voaj+30&=v!?a1d&+&*^#*UsY zGOa(}`Iwm5XU}JZ3$9#uupu4GX~9fgSvUwKlI_{^5=|Nnw+YL-2ctTFZW%@G&rH5} zw$GeuY#}9#bDWaPmh{7Qa`d9zEn#8b^>uZS5Dv2*eLaVzAF|h-> zKo=skzfA^JV)?Bo(?)m1`^}6t>&+iQXx<#6o}M%ftqGU|8y7MCB*Of{d<~KMZSTTQ zmyNce2(b6kI_*I3U)E!5+Xxj9p%wU{@pCyU>$BW1Jw7Cam0ZUB%y;-S{MGJa#w0Hi z$E|zknV*2PEZ_O~2ji_fQbE^k+tl|qLm~Gjtm^O8DB0_&YQUHAPse-O&^KAK2vEyU!3uXa2BtDx1ZZTt>HAp)*V zCXW@Ql~V6}^I1NWB>M5|!$sR3g6}ILy1JCVuUdL=%;R4!ZGVb>yjL9W{G^|$bc}JiP6t0M#OYPY$?ILj0Bb1;;@w*M=>+VIRND!v z>7Ach_j0%ObxMO>Ze{^>QsG=^kA z4NLZ;FD(&XWfD&&l=<8HiEfir+sV;pq9)8QI4|+lfz(JRZH-skMC52xh21P9&W1^6 z(RUD(MHS1nupE~;RZlD&#i35p4eHThN{JsNX23;U>B5@mOE2q#c?q#<0}^uWZrk?3 zZ~~$jnY2`mFCH#xT5=Y-=`&h@9WG`UTNwQO)hf#p{&FA>HvuBT?VMFZ@zKlvjW{nw zJ&e!McjWZ*yj_Ww4Dl6W+=I&_x1PTnN=PWiV(8|D3+)2AmN9>{?X%XdlW;Knkdcbj$tmi{ zU7ordsKSag+9>ltzaXbOWKzz&#(Cam45#)14lAU-1Gf!XubX|V({<%;nEP`Vj?lN= z*wdqLt}Gw#*wU`0aVr9}IL^I*wPK9LBm-QtM$&TFd`meJLA})`#l%T(;|Kwxx}Y7| z5bt#8W#^SOc`M+8!C2rj6Wi|a}>Ot^3tqG!6#42KeQwP zES&J-Q+JsfXhV}G-J^p+M>lMVeNoEVjh@WdHuW+&VQcB!b9BaMS!CU2OnksriLvR; z7@eO=9lOD`$n<*oMs0C|6uo%SmPePXWS2XZeivgGw|D9#|Kny%kSG>AiYs*N0aZfV zalAcAZ%_0tn_*{gC1Q^}ZIPXZ2WgURv2`$Z$;pwR{}}gI9_nrKRyL@%ME3&M+UTf9 zTtikQ15pOBEYDt<2u7*8xdvpqVy`-FSGq^2ZV2|5R1^rguy^iDKkhl5^PYp)t1w5< zpd^n`J{p)W_G?{5IO>b}E;E-Sy>+ggoUQp|KB|X~Y&#LjpHXJ>} zDx)N8qlSm}>$~opC-3&x8*G)ZtJ$h}ia^5?4Z!<{f-SEM^>n?eJV9d?OS~Xi&&$ z#fuIoT^AIJU-F;ovUGffKL7Y-?*3Lecb8nA#@8@I_ijmd5{^gJu7pi@#&8iQ%R@&s zsipiRbn0new+ns8@%g#AWtH#dbYQZUZRXxZbUu&Jk`21}GRaGH^z}@nW3Xqx!*hkZD9c$AeJL+H?`Db)Bzk z43tUqeXQr271&GOvW=`zN?H0Xdd?Bg>1Put+J+p%uQoMH+10gCms1X*2fM&_pBN`? z#rxJo?YnlaJ0+*Sbg^%3PzIkzu!!e;=X+=UF@;+goJ=!P8Wtc_-oW1TGem4)JmCc8 z8=iw%W)bh8+K^gO-j%b{$SO&Dhwo`LCYs-9EJJr~0F!0Aq1GoI$SUXM`wh1dB~Reu zHNMg%!m8D?GcD-wwlgd&VhF*7`JGy7ZkWCBD-Ze-W*~3s+J=(y9JGtRH~zXjnI~vD zmEoWZz#EVgNnVr!kOIhM`%0Zb^Af$Qv7lEgFFl&b1wJ+mPd=JK1Ex0KE{md+X+xF_ zs(C&o^cnkO1pVG=_0hoC{f6;%OuE(CnSDwRfwQ)53jdo>pJzV$QQ`Y0cc7kDcDw9N z!QY%}V{saWx+V-V&%5w7jg&4|TMgGYuG_(x1XHP)@SL5TOzeV_l;;FWSQkasKh@Qi zE+QBXr_}>6fS%I=7#lx-Yl zGn=2vnHfpeSB|r_Mn=cCbglg=N}wMGzdXcdkQAJZ28IoG`HV%X8pb1<&$Ds7G)S6w zZ=)MxHRNC)D^WnnbbacbC>ykDq_0B}lIk{Oee8|=H6=^+;{jYFi7_Nln`7qrV|6&;51--MXmM^?U$BdV-Q$Q&C z^ns3B_4GY4reKkwDBV2$6;ei>+%d5mUvL z;kla~0l3h9%sFE)&_7)${dKTV5Dan&%}nXJ&RcUr@pz5mcFqKOBO{HX(D8#UFjV{P zjAF<7GC2tudC956Zdg>m>1z68ao(-6G{T7p?Hx1sj`_TLt#Yge6AyM?bnX2SVrqhn zU7{WS)#Q#9ahJVEFxGZH8{H3|ldl(_3Jo-QI-^eNf@Fw^1Ufj<@W7moS~GAFN{x*8 zVIz~LM3II~f52VNP;!(#`p6)H%jN7sq#s9VO2lZ>NxhtC``CQ78a7rvv2N_=aoREF zWBy!q0?@HwXtAD<1q?Eqb& z*+G0ue_wLrbzo-NQ|Dl$?6X}~jy+1rU!%3r4$5iyso8#__O@-FKh|j+IK;?*1|6uO zY{*bbF30doEsT}rqv5b{INJmp*dh3Mp3sDQmcxCztWh!H`qS)Y8yojz8-g?QXXZ)tfGASx5Xgi&p3mrU;MAF+! zbwYe@xaVeM2>wA6wx|M!x>jJX4duf64*Rx{T>(oNheg%|=5rXKx|@H6`j)VzsnxmU z?5r|(*SaenvRX18Mb!ws@q{DAXd8BcqG1E ze{5!7!@-+P+x)Acb{23yYTqk-pnaiEc>@pw(jBcCp;HfMw*?YLTC|6dZs zfM=V&gmbU1w9l@L7-6ci@f2s%C(c4i(Pf~SbP$ADwMJn-E46LAhkx3&}t_CN|aUN`(k15;^VG99I?ElX z8L^>@n`=e(_uQYp2G#H{tD}C9KB+~`mc9xcgH}?jasQ3$`PHA(@_X}@kr{#5rr8~|kr!E#*+=8E602!E4) zk^v;Ckx>|I{mw~VnAXpGkx;!DOJIcFH%Ds*!y>DnwauMQ%PkT|DMtUa=;82iT`Jy z|6c|IB^)g8RX`YZ)_>1-{GE>S>HV?~ENZ@zTaZGQ7(*z{k!G!p0S>#VJQ(@m?OF2@VB0YE+skB(fSyx&X<V5`(cSfy^GPkGwFj-G8&#K`oP<%1-O?5>uG0aw4j`Z0(s>BN>mkyzD1XOwe>S#D zFE-qGF>AguD#avoo?kIagkdTUrwZf_ZJrny0EweQ-uz*13+$|0uX`dq>3y2KgiIzD z{(cIRvok@@i~A0M*%5$LyVT6h*$(EAz~kK+csByxOO^`kpg3*QfzlCxt&3w-yAo*g z>w8{c)s!wzscag%VeO2rZP)7+-{*wXMZ5EL_FRBdw=*{EVQkYAkJ!|2CYiT8PU&FR zQdv~bd|U|dWi0ppBqf5sL4Qh`})jU?k|uU30>E}o)u*oQ~}lx zdP1}v4j1{1vy2X&h#}Z|9_8Qt3hiorRgpp=b1Je2>TpWx*vGW z3y;v_sO<=#gits}mLH}2p-N8O0Thpp%jV0bai&@in4?F9hT9IwjzQ{gi&N8mQ~M1CkAwd0s5ijU2qKH( zWqr$NTI+Gyup|@z7S!^S^TW?CWq>CJ={=kztW=K4B0+dhrTu!em(DK$f6b0$;>hKU ziL3z_81rUR0^Fl<*Dh9GjrY8lcB?#wo)34|D?o-vB5Y@Q(W+s8 z^wRyHj*1xP{dLUYwdT6JZC^hy(Mv~l5KqQ3Z(Q*wQo_{DQ;Mhh+*M6_@X#^glI#RYB@-c8M20#RB?FhxDZ z0iFTkQj6>EsH3COY^=QlO+ee^@c}>|Nn1}z1){DZZF0$Isb=HiDi;dstWMcNJw2YN zGL|agm7I$%Mu~jxP8f4@78Np|nC!r#s)L9NVkYxXnTf{yArlJ7PGF<#?yJdeugZM& zmYZLN9f>SP7@vWR{M);w$9uhusTPXb;oY0LrHAS=Iye38ri{z;5pg0E1f&?aSxo4M zj`{Q>p$_8Dp$8-QxF->?$&ma+|D|o@@M7Hoh^qj-`(J7XP3MzHwSpV|)NMBg(Slke z^1Y51QQmi1@(WLtDV+od(1O?Pe!zx$9r_7^b7RBRAsfAax8wuFp3ekGD@-*UHfv2c zk1_?n#vrh{p7q$y2pVlZ<;xuXF+k-TPb%O0_G~1|i(TdUVY$a(>?E2&B=s`rcB)cHVXE;bZjl5(H$P6-idandvw*$0`I1tn*%o6#qEaK5)Zj)FZ z^VDkyK_lt0Fp0{nQ{iU)VE&P6+iBR=h=MplT%*oOeW}L=odLj!dk;3uOR!5Bc5LnFUn{W(2;}EY1eKw8|^DdV+A+YjQC#aA(xeL@{oO~GdH?{?6WJMJ&tDPZI7J5{E0_5@{Fmc)@aeIUrk@{Pj zi6F;MZWrtDiWGBdNzcPTkgBJ|WN*^@Alys1z*6=emsZJL1AfHuP$)>@=oVZDx$E&` zPm^ZbV=7I2~Ml$M8VFNrU@Vol_da)EW-wG6Ih$-d=mS?(1=8hEPY!@%OWo=$DN- zA%Q%Zsmt8juu^1`=x{Sl@pfgVWqR6+5-9sEmHc;N10L7?dL4kQu2;27cOtmTR>EU~|URIoiRu3~RF8zEL9Q6@Bc2NI_?9FJ~NmxvNA z4TKlNln(Ak0`?T#YRWEL`4Agz*!ib9s|kE95#z0DS}}56?iGwE--Oza$mi{AIb3h2 z?CWa>s!y8Ub%8QJ+lr|V;ou$j%6YLqtC=-P^VEzGe;`{HvxpXSCl~DSXK_$R=Rf%x zMea;#)d;teVVk<3>wdAPadBkV%1Q4j74z~@u6Y*wVh?NvDE@G_lja)06shCFkb(F4@C+!`` z3C^pvWtNd7Ur!TmI}`2O5vj75ixJ*v2kcyB7L|JLn>!j7$IdMpW0)4gG5^w+$y-B& zNEeM(PT9zrxG?XRNzPg2#2-sW)@3-ES<#)~)9t@pt6u~NH66fizT-fUa8URivNL)z z+jofddbbwHmF~jjkiJw|YW5tvnxv24*{6~1!a7XJbMaJaJ{8w7ig>GtE?UMRk=}%! z4$T4(-_|pGWD=&9fCTfa!ub{B37!w%sk_FH`-iVo@r-IHHoWUkPK4W{DFHjkQ6qfAgQF={Z*G5&&A%RvQ>r7_+Wf~spynOk~P0=%143w)J#LAd{n)`LMUiN2uXj3n` zWfFmiN6?S!AP7Eoohgv9CXa_ze2}GjgSE#bUNe^xWxIDr$&wm3#Ox1>z*KC$xRUL7 zR->ud+?z{8Va<&S7q){yJfcs!5A6TnjeHNseOV;5gU(VS3Hg$`00ktT)-4p3lt&&z zu&DUkwnY~f_;~-fS(RC!Vrti{V_{)Y<{)-N+o9M{aySGdJM>-Yjlj2jR3$8_S=l+| z{_A#kv4UDuK3_KL5=*&%g>g!GRjZsl_Q1xnqH?c)5f0+m7>1Tvo%!?>N?)29T~yai z(9^D4z@^EW)eATYLyqs~qDg4EM}`KJ*f#IC9Is>Q4TT_MO&c3Bp0ZnZv#Taynv*+- zD-aP@)P5I3T*|$a8wfNCEX;Q6e+Twl7fhi2< zIj8(aJ3T0c>?}=lF>?GG_0vlj`P{yeB^80Rz7$c{sY?J|(gBPm&<}8-mMGM&ECx;U z@p;wD+N~RpyM6a@RJ$Dm%3A5*%@L;Pd|gbTR?6G$Ux)FQv^qo{H(Cxw9tQr}1wHK+ zk6U&S#BgTq=e!Nuow+F7PJ7d5jcb|)+U67xbWrNfvas|WaH4$=VVNzFrhE6tw9Di3 z1gt1>j}WdWWk$TeEt;*?V$6${>&|&1JboA18x}#6>BwZk8JWypONum`%>*=p2nfhD zfq9C1r^dG&Ja0cC^#hwL8(2y9%+5{(;Q2mkZtp{&d2pgyBN9a+NK~=fRj+Se0?(i2-F6B~648SgZ`&yE^0n5>B~S$e zHlTXziEOQH`KEB_^o!`$1#o5~N>m*o_q`DF*(JlXcOiUD<)D#D61IhYlb=+rWvdbA zL6!T0py&3m#ao@hDfFw0)q>~cLT_*qU)yMCH0S5+QKu*3^b7N(n-s=Zd32KPDW5-OHUO&6$+OEo;icReg zviNEpG~HTaVX{Rc)OcuSG4Zr!pql=M$rrIl4u zu)H}uRUJ+0pYYu*69AFi@;Ogx|5FmL06-+hXPs{9|HM0ny?`Oi9oAKB{!>yG4geuc zSyYo9|9oT1_d+V&-fRNGf4evO?jinf=;rSTQ6L;DaQz15IREuEi6L;p@S!8e(cb{s zKY-LhDShB}m0{Eq3A}QFx|4(Fae7k4{?GaPA{YaVHaUD4=8tNB8S@~3DJ?egGRXW> zck>0j{2BOw_1e>Ktsu|Z=&i1kln5~ zhc5@kM^8q14MsADcNeydn%z6TY*`_IHwWo0^lprGqkIkK4RH_rfWt(f}#g;Kw&w^!7|N&w`6AMqwbPM2`gc^9+W z^K92ZHqF4GbD@QyAp+@di;{0H0GD%uGbrAtParxZu`xJ+`S~aOAgP$TFV;9`sibxH zF%7!%N@e@QPRr7@UL7kfxe$k3#AXcOO*+tTi=@?7mc`5ZI&&Bern`sLu#4U78%*KJgn*LRCf@}9 z7Bjw^Pm7#xYCNT{Dhab|#D2#fbYYGs6o$*yv0t(2Bt z99kWH^Vx=&9U(o2tSvvrcrotH^Mx<`I;!+qw-i8Y>Jdn;xTg6N+vi*){8EQu;pZ!p`7xKc0b zAonz99)~$)i*JLNCcg3!)tG1>+^Bi-)!j0xkXVi9A~e<<92h7B$r}7bvPE`G(Hu}V zf-$?P#y0$L`Fbq!MwA6oC>yG$!4tYU<#ZXi+OZ2yuP|y;gGFUyWtSzr2 zR)rsT^5$|*qR2Uwn+Dl?{cf1Ti_sGIWZuz0HwrpKs`BDnJPM3a4T!YP4$bR$N#PYJ z$XO~yr6i5J2|MiE?$+JAldAph;J!l=4UCQTj`LO+Tk_RD!4`z3CZzu_ipR6T07pDE z0Q<#@ugEI7UzyY{YPtJ9))D@CQc9flVokiWyfP{`T~$fU;x(CM6beEGXALjAG*Pwf zt0QcGXO3&bL~fU2uIh^&uFF%im12b;a!wupwj*&S+VBi8)+rIYnyEHAsMn(;oO zYVZ`<9dfS}Gc+fCwzG7YfnI5HyhK>zeDFwIs*>Xk8z9b<_rDu>)`vQy_)1ypNsJzX~>B{s$f zJO?9XeV#37;7!@m$E&Ofhk5a4T*~4q`(IbvxKXY@?WcNtjMM_1Iqxi1huC%KjlA~N z_#HJ7fcF(0t$0B)Tkl$b^pe$}(DB|wTnMd@YNhNW^UxNkQ)9jxw3^rS*k4@MK6YW0 zHlM3I=`HPu8>~EMoMt-k{FpSw6O~Z%2&Fxy408veikfFEXF?@U=m&FHWZ|fI>AS3c zsb{=^E75xVC1TGZ@Rvm?UI}=k6H|^?p}aTWwbDfO2Gh%ZrT((zDKqzKGLG(^K}4NHzHy2@t+!_p5Si5B?$@c?(P~Kf+n~X4k5U^ zOW|6$1(yN}ce#_j&%S4^b)C9Oym%rVE9c*s+S{OITvVeMCt}bK2GU(O4$LxAy@K@Niw%T=vFe^p^9{+O_Osy6U&(+Ir9&|i0 z?{=tbkaO9+i`w=LjAl^qR9R_O4<^=nMx&86)3l`tSH!1cIuY9BgFAf(!&JT(dv55y z-E&>ME1%SS!}ZCnj9BODjQ53Yz?YBxL}DbY{t7HNRzuxk+P_$P>2D z-Vp8}B{fcL$^2S3e%5wQ^?vO+`R_MAA9Yi(XJS3t?wwA=2(H^QLb8;!QWjHho6jX~ z(nwn>Ttlb114*-vSHs{tcKQSrU=&-WjVDd1egU2P^D&wS?|+a*JYg~UzV*E;#qKWb*C7&`BbJ3bR-kNKQga=*64nQl4^ zHR?BB{Wf#RR7^zBrljRrH_K42qW?y>T9oCKSTkgM&(yIaX_9i(z=pkUUPaIgLK0A=bS(Gnu7P1kW&DU(nZ?Q#9^E4pgWYqR%Q0Avn96ZuDeD!xftq2IcBd76W)4=s~fEc z!*}6f?N8>Y!OlnC>)Y&;>-`eFyj+pix{dOt-%KZEhUNAb*%IZK3cU4(mD`Crlm#0b zLMwU`X|*0!miOg?dX98Xx5!@m+sMdFD}pALH)6%do={hF|PA+Q@7Dl*zvE@AgC=3BFhk?ex{9#*Fv+Cltl?{#L7%n?Oxg+|>#ME)7oft{ zEk!c2{Nm;hS>iOVYJWO2aO8!4Jr_rncEabV6*6W52Ef%M-NP01K94%&|E{}g%g94e zcDCC09=Q}=gZ$xMcU3Qo@f&-%G{I(>g+k8Kq;Vze)YxU8B4i0V6tbvKA|o??&1J0Uf_;Y&%gn1tOu{aA`z9< zh`{@?<<+C1q|culWCGhhaN&%>8aKX={}0YuA!{IqvVOK(zv^}J?x_|xLq)hyHx-?A zU3#HE+!0>D=C#=pwMxe7greGZr4P0iKVJmGuboewXdnaplAc7kRdKotgP+L?XaX17 zE1ULwzMBQ~k*@Vm>@m%E;Fhg@d-fLrInH zTJ(Nflx%IDZLgS;$U6x>BjF4k_4~6IfJj;zrno2Vq?_5GasHeBHcH_v`Vdr>2wKP5%?8@55a$y^qY zU+uP&6WGA66XbcmJa_hu`gaxo^~6{A{*uZmZ=ha*7Yuu!oysM~nP=s!<52Y~jMR_M zXPq@yM}YJt!iV+us3Y%EKlu45I9QSq*_yygPWw3yTh{O9^hJpmv{{_fFKh_q6t~-( z3D~`7aT9a(JU36csw2!`^=G)r_btvYO6Nvtd~oN>^J|vuICtPlbbQullvD2$d;=IU z3+wuTLQ3Y-!R`-IU`;~-z3lH1w224jjDGJ}OS){HxiS^6v$_B5?i6+Aefr~yP1|af z&fpHP&92Ptw?l{qc`jYK=7iM`5hGgpUldxJ2?l+}?|=tec{&jnJ(g`GWVmdl0u_ay z3a#DR^l7IKbm~A_+c79;74f)cH700v!*gKek+H-j#Laelig@>_@yP>7)NgU1!xh4V zF(Qsw?U|<&pth(gI6>U$`ppdpY39; zDy-*X7*<-g`Ue&tyf4Es*pW`g_1(^Xc3&wo2e_`qD6G9MIkH+(*Bi)sb9GX3EWrFm ztwC#fAi-IBJHzE_FgsiE*pTS(nT$ zuq&dlHY5jt^m=)!X7MB{xBYPXPe$&;MYY)5?!uLN(a{6mdYyOLIi6#L+>g`>i5FW5 zA$eX%U-Bac{c9F`@4<_&pAX(shbh~91|~73JYj>k!<$i-lb(fDMbKoss?D%&(SyrP5NIaEyr;Jse^^T=Jl@Qfja7=r7VlDNG#hxZ&;P1@)Jqro9LVz>j2#B-_ zrFot7uhy?ep<0&IsJH>W-HB=*wZSH*T^zHk{mMZWxWUV(5O??WQ1bgHPXyje3}S*- zz(W%PsGeMD4g(DO>CV$K*E5>7Ej+hdopwM=H8A6T-xR=V^rV-N7#3XokRtsT@gm6m zbSp^Hrhx{qBJeWUn%{ez?O;do?fu$#!Dc)VX9q;wX>I)`ynr@N4MNfdUN~Eamt*@} zJn4g`n?9_{Ky$6wW2u3zq{ecG%9U(N=CUlZYq? ztZB<~ekEUSn2!RgSo5&ISz-Ugq^omul^d|ioIj^+c*|3zxWWT$776#A$jguJcJAkV z&sqvc;@|11WV|+H+n8g2z_u0>N~TPjmA37zB@qqd@4l4+9%i{w*ltSU6)8t!LtUhk z7{YFr1MKLL%1G;PGSygV=Uv(oarYtk<32!#o^~9hy++cdbMJ12U9s%C-=!bh2E+Ye z67BXrJ(a05=b_Rus}^Z%4dOkJn&l=4m0Wa|^f*o@+F5jSB)1ng)f&8EH@I8m(RB?` z^GQ^XooJL@MzX41-PD!E_br~*Hkn>>IRGH8*#!O-$cgHIat{%*|z0>$S%ihq2egXBK&*4vJ&mQ=M4*ia^D9VT;)K1KVU`Zwn-6%ruX zrBx!vBFF8Kew^c{G_jUl#|!=GVs#mE-);UCWV$pvF$cOupq2cST*_`;>@kPzp8m-d zgZ`9S{!EH+3ABBPu-4jcmtddE$q;x9h_YHQmpwxm%bMSPH7-bZLAhM<_2qfET7RNu zC?mp=z;70l1O6C~lV>~^vxdq#d@r_!l5ttunjaX*iTHJ9CPzfmHr(EGEn$cx%Xn8E2g$lo@Sk2+#` zE2(@WQi{8t#TlpDJP@(fOHOTf{M4|ZQ*yP2L{FZjt^ZeqC%)|_}axl$rW4y5Ka$Y*yU!z{;}AjZ6ft0opdd~9gA zYXiNyG4#-9*GI5v#o)MaaWi6WWb^&w%|t%2bL-CoesBmfw;fZv_p%2Q%H?^E^x4raq`{y9-^#-4j5Q$Dyz36 zo_u_>R#n5DHcdTml(X@K3t-+bPrn;x3vw*05oiIz9#wg{@(c~%HNOvA9<B9MYv+&l+g(DcLO?LHPQcN`K1mq-v z2n1hcDDe8T9R*Uc~Ch-}>sZZLv{1xU{XJ zCrklj`VM*zF&EzsGiVWoSX&r-`;)l3b@Lg{C`{p+iB{-%Z|*G^02^$&L1ym$UjHbF zs-$wlCDHB2wAlMTw}55NhDWpr(WxEhAmmR-=I2nj2ua zL5WY}u{WsvUiBwG)dmIOM-yN&{3*w;avaXuS|3w)HJB?C+X*<8)~PFz?ZB3RflF?U zZ`<&DIuk_w0>I65Ur{f0hGJ870#o&8RS;kR;tq2y-s5!!EQ4rdLIO`t(q>>PyH{l3 z{7QM2exBzSdP;CgocgQ!BN|NgL{Oailq$AWZ)auIi|79rZKOa~EAu3zwDE{`hpg4z#-N@TwSt0s>{| zR6gi1~i z!1^jD5dhWz&1^e-f8nIlOGgRX+2=p3H*6<)c%oDhPDb(H0Y=X7s^H}o&w^*dYAvuv zXsI256k}z_Z*d}Gf<{<_jazvrh609#Aa89?={ZXMb zo9edTRatiC8Ra9_t5hSgms8(6Yw?sR+N-6#cG2--{zED3ycykXzwRo5dgD0WbXr1C zWfl@g7S3~c605pG8)@zfRK1@g@@s4bXcZZ>9{IX?Say1p1;uw})vg>vX@Y5-kbbljM`)f<99YZfi6eWjE}ssDFP>IGMv4 zhxF>*h2e^9OSZr(q~3RT?&tvb>JeaL z-THv;gTr$OAk5HIwVl6uV1GOODaV4GYE>!YcC{J;wHkCg1ANILEf@2Q4MSB9Guo~t zv54XB_8N}EoReyZGTJl#=TOjF)M zb}e+6ZB1>DCL=oAv8yqgy+3C9zpO?-2^RR>7_sQA!t@OnRdX8zJEav6VsLka`ir1nuL9guqWUO4$)5VMSNQ9mmrCJzCd;q6 zx$*}HZZeU<;=P=#d8r69zW`wCzma9;I12JYGq31NRh zZZvq3`O1s2$?X==M`u`j)m3Hec$sF^YVGx<1TOcc7|{SPF548L&BlzbulI)?J3}yD z%j`+yAMjUD;JIgC3GpV@esuHs~LdW)zoNXkIwHx1PYNP&`?NK`Mg(=RZUA1=roZPQLY`$sA- zrm7~LWJ`xK*A}TLX#xO$#y51_d zN1ZoY=Pe}LviqfIPKS%Wccp5HiI6c!Da}&C33K`6V z!l343+Uq{KvUoyN!EJxQLw7So&u5F;wMP6i4;ws3rtBLr#}aiP_5t?;Xy;_{4D53k z%5fO11BNSGB4r*#*in16&Gh)i&9+5wZ8p1TU4ec%X+07NdLfH>&#CBbkNF`#G|{kD z=&eng{*KUjiP~e)#PW+9`tQd^F_ms6Vi+v!TjT;Ua z!1CBbWdSF#kdn%m^dA2qJwNkP%?-V-0 zA*YweA-jbtANeSl(K8P{Sr~BpuRcN2t^gnK>wJY&d^@Gb$&80rfNi>_nka#43z-ir zBy>ABny&~CKej5S)O}^^wNA7;!Vin#;OG+> zHj!MUwx8q|Q1{aA`KOqsOJ8{>VMoO57q zp^5Nh)@$dmYDS`;NpdDB;_KLywzB*U5(yzlW_1Z=m2Fvz_+&Vcv?V`>4id3joK1@T z$vb0zASv(;#zP5eXxRO_*~Mi{>L+m*1KdfHbLug>Q56=7NWK$}zP2Xiw2$(;9NLa&%TuI9cD2YoTYb6-1LAJ0?PJO-{E z>P(P?R)Hz5>v1IOGOJ<-JEK6|sP0eW6rztaZ(y`=gY1y)m%=vTW%;Qb>uI#bZK^3h zi-~9UUn(Jt{CK=oAMi)=by+!$C7L9xOCw0SVQK2HwVRqjF-W&pR%_TT2b5){86qp& zH4S!IyGLyOAi!dq;E->zp06~zlfMm1wt47r_B5_jRxXeT6`a#9P?ByAW8&MmhOXOd z>GXZ4@xJ*EEg)UFLX=eo<3%^TH98CbSC1G%5%au?{osvb!RcKI*Es;-RNqG4@8vlS z#!0w%^WNlDZ_oQg&q634%K;?BE0X3y;fcP)!R>d=Ual5GFK_S1nrN0oVI>!dzIme3 zh-UeK8>01^>pROz{n99OS-AERDJAsNZWo7eEd%ml!UBIayM50nT(o){+sK^a8+Y~P z#w_OziW`r_IT%=bdWjB-^%x;lz`SI)hXtC0`9nzahZl*%xVVEW1vZ`yx(Yq?4(O&B zQqGdH907VucEy_Z3JO9L?^Y7^^e$8HCd3~r0_${13Ll9 z5`l|5tl+YZBPw-{qS#|JIjDtYAHr{ zTjWAQMwzSRtCq5Zv-J(#JC;XI~RxB^G`f3|o}5@HkF$Y0Hbfc~Vp z5wtX(hV{RVmJ@8L&I@ap$#~o+o2Vw~T5H}uvs=&lNJnVC7;5(G$?af`u^?-^n0I^n zRypssq*~xqCNt@o%2e>@#N_x# zRid{AtfSt_c6>AjK`+`4@eof~6+81ouBVmC3Rg^woXr*9}76A;4=KBA?$TX zRZMnGD(Osfgc1*BZbL&$^=%o7I{ycZRGoU)Rc;%@db8oMQkXonqd7S*EDFo*J$})) zXIVFW!9oF(qhb4N59e^QR{^^9>@VXzX`Hp1E6#sZlvd%pw#MP_{dXpDEEt)Mh1B&D z<1~@OpnBCJv1C3o(I(7#_EZ1Pm&kG!O*K^3TxR~eAE&W1W~i|FrLUsT*g?$up(jj3 za^WJ~j1alPFPBX{-5wQv;zuVMuL^SYnol*^E3f!deb^h(6ux&%fuuhzA?qdJCRl$4 zyGLHqQ#xSh0M>*x@w$^z!Pl?xzysUv3}M-zfe6C@hqs4?%%!a7T4|SEL~i)!?h6x2 zOtbDyFYMpqz&mloVJ$EUdg%;^2{uyC6w%bFF=_yPF?ASLK(jYrRM(?RwrGw+%NeFUR}y45bzdaWG4c!dhS+3HbzF|a3mbNDKm zwR$twVwr&(*YrP=YuO5Q{=z#Z!sd4SnXp~fM`dUZdgF-p-Z3Gy(Qx>{oWF}XY8e$&9&Q^ zJ>55Jh4emgi)C-b6)Ara_G%;CcG9;ef6sP|xC@O>51dNp*qDP(@F5(H6_iSs3Pw;! zg|5?raQ$D4%EVn(XnHe7+;_W*XUxK}Z8Si;x@%l8VBHn4{xuSQl;t_2N23-}EavXW zya&TW&BZXRrPGxN$9Pv?UwJpn(*UAHE1>h=bA0|7aSKiEEukt-<=5v0ql{i%T#swF zX$ta78$R&6D2mGT+BWO{iJyFXnI22oyuIQk@X-_}9T9hCk=NySvo?X$6y3|60Csys zB#{~Wj=L-Gkhr$2om_?hTV};SjJnsdR~vM~Z$+_FsbTU{1U#>x7X!GAg%w66L<)1Fh4|1&1$gcNhKoX{&rlM3kRhdTrw@Q6 zHh<&DO9q53SI;Julx_{saIwq6{1pmB+I;TeC*A+Ze&usX44rg(g~hP&SGc&}<`0qZ zu=7@1Asnh=%+SM$6{R%nb_7=O63l4jgqDU)eTHAe20z+OtXH9`#lVp z?DRUlZqD%jY_IebiHnw<=NE#e5Tw-Cw=!~1*%(mVan`&y5l5}<2&2W|e9T?opR&Ke z=J~QqMbkQdvHwXTfegw>nC9Cw0!aWAWw9sp?Ngo!gUF^{KSexwjtl@cr-|tm`U1Z9 zRZHDljo(xWF4^J$mH6NV`*5|A+Kx=9w+=6RI&LS(JWtwlze4JXJx)~K8;f-Tu!7ZR zO*48#k+CinB4Rw9^40GtD z)=;zoh@lf}(pY)X#-^D!L&%^7E$GeB;0;5i!;?*is-l7#zmf)dlkQxtC&$Tdtps0HJZ zB)V+{j3A4)&07@ZjRZZ_w!?tl`cPN^l3h!?N%z<*G#7TF{HV%@CI6RsjiWY=T)hmj zCX4Ao(PUX2SvDUSawrng!hhj0vwI$W`@m*+zv3WqbK}U-96W+BX#gKSYJ7dlkdr|1 zEy!z9fg2r2l$yF-F7-Srbn_ZQWR&y0+VHsKzPZ;fw0GTu7*$R+7?X0(7-;2P0 z1oELIK%}?L?g@X|K$_vcl?=gd=juVebj^q9t?&v0EoYg1U9aPw!bJdGEw~S;HRd*^ zdmcb{Dz_M55}E!seM<0-`dn0PqzJnnhI9}+y8HYU<*(;XS?>1n>cpv=v((5T;*6G~g4=P=>n=Q1tsh#)k0*(3)N;HmhnqMmcVIGKfiVib zznYQSPZTqIdW%_MnSwsZ5Py*}ofg9MlYKyO!gvbZ#WLPLfiQXejqKB2G95^mgI}Zv ze!Z(}ySKg?G8Mw*nlp+OY3J3lAK`-*m)os&R+A=P07TXdhO31@WZVfC09WnkcC%Zc z{H&?^PXKrE`V&F^ZFr^=rtqEJThEh?{+_vsOStDR1I00TsTTp@e9%bXR~2;{cQm^Q z$Ujz@ux|9fi__ROLtjC-qKhK}r$ZaHtpL=Y23vlt9=H)J$szrNE>BYt z6>eQEI7TW7H;4qvxA_i3sKEUc9~?^`H4$~|^63o=p&xfJAbzrjsbNDpXm(g8tfMuum^hua7RU2DT>P_@a5I74+)VqUR~ z>yf#aa@RWqdFk$)GNJAg!p8Bm`GJNy&Xm3H>u#R%<=uRfeetvsH%~`A@Pz~F-coy# z`}3ixjy25xw&44127WW}<^@tbTa_};W}JHqaLEr*b+5#5AP825X*}4=Qc=XoTauneaL&6P!+s2P@Y&`7D3Toa zgz&8##ubVPZu#F*f$Z~d)8oZ`pqcacQ(@HPGkXebaGL>+3K4vSC|E4gxOu+2mRf6S zkcR03_~Y&-sJm|f6+gj5x$ehbascfPqM1&6Ts=1q_Va?$|NU3}+XZYkh#=e32)8wV zGFNHy*Y)8k7*MYDl&eTASPJ-Oa>B1x9&pDej7B;CGmqf637}!T8w3tWtN-R}erjtQ z1J>qRFPGvcf5P8P$V`cV@qBR8{4(<|9?XCJOeQm6;I7-OGFa=QsTrf5MW&s1P>ijz(U!f_)Km9-A9|>~6_1QeB_8j}? zK#V0kMPE+lD*hiGq8%-b2A}(zc$SmD2MYc@%AC|}_G{gId6w#B0bPH$Wj^dw#k7MQyyfZC~ zIQ>7PG9N>LTLuHe_kR*#G%|p9&PV+?|7TPNLIGf7{{Nr!|HdeZY)_Z@KMhif*dI{K zBjJ_{OqV`;^0CU5^v~C?>O?vWxpdSNaPWR($SqXldZM4Ofh|28>W}agEss#mRjtF8#aHACCnehr4Fv$0NC<|UVp~=-^L8k{p1F!z z|DF}6$-0%~(q>)tGvz09MjD$_c^NIiPw*&?S~H%unXoBAmc%njk{WFg$J|v@wbpgX z72a!i?oA=&m}h@^iQr)aHxp&delUVXtO&FxBbf(GfFhP{U2VM!(Yefm~S;u{3(qUF6C(GvcsrH zeI~a#1*GArMgH=#yF#F!#>}AbB=^&x8zUE17OQlf{XVhvXOW}mGMNbtkFh{i;!FtNFNRM&<3@U5mM=KE0(vn{SU$l)v8wUH0Q z(+UZJ&cX{epY+O$_uOkh!Wxb8zMlvWwGKJfx&JcAz<`TzVgcj)^4I=P1d9}r$@4Y~ zl}qH!WiFFjgJ>?`*X}=4UWu`04v~iD>B?R$N8*&tt&#vlNp$B?RBP(fDDN-+4wHG0 z-Azs5IQA*U*WVzD`WJWYuE*GppcVx^8wka-q<8fX*1e5?r7|cuTSy0YcWd9>xSc%F zlAfXQg-DesT^^Z!!12H#073fOe_ojnI6S+#u|@z4FUywgS+j) z%2H?$em>F1IM$LxuR{&-Cc4&e+CJGBT87D7(Pf?UO1l0On?#g%UtiyH$CO7X%sGHY!{@qtH$ zm8EmUtDSDd!#OQFQH|0oNv;~5P=kexKvO!(q!kW2MEoF%1x+-_ zgx-2?e{Uh7Aa(&$S*7b7xv+S&!ldg_&~h6@PG|Z$?Z{32-H{^0_~pw~&pUc;DZ%lc zDU1`T8mxBym1TpN`;mIOpN=-QbfvN1&&{q6iZRp0FCx30?|qvm*9lzzd(^%m?2*AC zodc_HqCpFxjyEu9Sb;4?V+k+yYq#(~&1>ygb3_X|J=tx7pIs#U4_FDEqK(UYJJN@5 z1|qP_evdGI5^a=w;bp15d?|?z)@Gte5n-&-M@0=_MM0YG*63)=zp%%Rlm3nOF^P)d zKx$x3)OGNWZ7B{U@xmCZ#xYoshIdtg`mJ?5un;HP`TZedTXlib@xLAKt#xWA@wYcH z&?`VOh@$JD+uB^iVjq__nM-rl zR`CJgqvrOEsnpe~YGMKUEmkT|$LFB2p%N~9UvF0|Xp&tAe}Hj!^^|>axsg$l8uCh0 zKgjn2dGz;AG-O*-PmYAG;v>{+55h}d zPlce!0gKpbuZTa&3Hoa&WQ2cPmE3Uc0vj*w4^`3qKG$e*dSCC;xKBeHxx7o#Eo=7W zPBCt+ej%ooq%(Qb36pD^+%2TM;t2f+TheYU)&B4sMYh;9nCGmvxm-m>lSMDqf7}u5Go0I45L~V>&iAB0F^t2F) z?{J@xv4;9%t zzNLS2bwA8iQ6WQz06bd<1*4JS78lCuInG)JBNC~7CedK44jKPhnEt&&Wkz9Tek^-< z{pkR79^f9hlRj9?J0RU5R#n$Dof}eEFe8zTIqIy|i!TGZPw-g3Rr__LbAH!}iz)t# zTc|DVHt&F6_(ri+X$)hUXA^U~+&U+r`Z8B!;-sQf2GJCr+-8ut31ivCt(hXlTfMW9 zp;AhGgWD_}O4=6r8Pl2nygZ{==jdx|iY5MYT}nBSY4;l?n4*H&wn6_gSAXZ?I26bW4l)A^*kfl_*jjkb;b!b zkuusw*ce#*yEh$y$kW_8N=7J%Gm@Iy8VhH$6L}a5xQMbElcX*~Wx0KwMIDvdr)s3D z)@m=yTTk6fyCQ2xejF-ixEwhCbs-0?SLPP-70zBPol*j)<#Zo#D%~dF_)D-}kmb}K zjy~*bFigjRMww1w7b3~Is`JiuRNxa*%t{BoV731;PuPzcIsWmQx@l16njXntfOM@fu}?J zm~*uKA?}XbOH*FB)^uwoymmUZL@aWxmHX@GC~~*~oWTA~0ZniCMiIS8ia`k{7D6zdzSz>TUu<; zk65`<2=SmaK2wfOz+Q+MS!FPOW$wnTIbiO1w6Wzo4yvvzzh0^$iFt8{E2X!0 z(DoA8B$k{n+5a*O^IunF58h8dImkgwC9Arh_5QJAKJ!`fD^mJcs<)SqwF%6c1D>$! z5x8SO5JbIPS{4;}@` zZJ0krHXk~9`V?D$e#1PJKizN{*0%(0P9>ghRgUFntZX2EMXW!MRcmtlbN0FsZn}>u z=UNhP3<>>GRq4S);rq*IpDJ(v$r)#6mB_mN<04Nj22D+pvbnj`&^C{v@BrDWWKUh( z?B$)-SW>o*l-KjpGY8QLnaxfx6THK+ajUcJtnI#`sFaO|w`LSk>@IY;cNW7|WZ9tW zxTpekmkYRcTjr{jrO2ivEO7Vpfjr)2TT`4a^=MGx+)hXRYU!p2j2?;!oiwZ)p7Bid z*!?7n8H+=7Mq(()|E{TQ=E#GZAR>cJ{4HO!;<2ZHU%;lfZEeksG_5ohu%Y*B6!~*g z?ezwv{)av8!u^B4(!!mAJ3m+7x|TaX&~D1e&CPY=m}{zA>&pT@d=jZ&@dNSRDjBTz9WAyk_IE*b1yc0kqdSHG-{voj#o^=HRC3t zc*irqL==cjX1yYm?d|Z?N$9Z#na^}|X}uyS&Kv2qz`xF6Hd>VF-0~@@DkTD^*jx?Y zLqx5)C$|IXLkKV+XqV|GZTkBAJGo#;fsuNfrjcRDq*Gb<&sOL3hwRbf(-DPF{6t<{ zzssfG@DWVs)oC92O-ihBydpIqk=cMpb=ZH79SyY`VK>Tx-EA|fG+HZM4mwe0wcqTq zU6SD5;H66mBUR}F=OGNfBLlULPml56pQb*|ev9yD0(;A1OWsU8b+mnL6Qmc~O)fGP zZxi@Q%m6-Ku@)drPZX=a@9*o2sW1c&06Mc3x)Sf^t7I zjt$&_Fow}&{={SShYuh60ZtzU&~Hh&yYm8U*h(19?9nkI%($UZQ3-&hdPJyh$t4w_ zGfJwIXc+sRH9dOz)g1z-J;pXS3!Lj-^#hPg2D%Kd%0D+Hnsoy}FM?*WAaU!ea8qjS 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

G(qgOTu$m) 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≷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 Wh|FCcvA>q{&b+NtCAx0NXLcs4eGB(z* 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 literal 77035 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 P{JW%lMJfBdng9O)p?7;+ diff --git a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx new file mode 100644 index 0000000000..637cd40d6d --- /dev/null +++ b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -0,0 +1,134 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { Content, Link } from '@backstage/core'; +import { + Typography, + Breadcrumbs, + Paper, + TableContainer, + Table, + TableRow, + TableCell, + TableBody, + Link as MaterialLink, +} from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { useBuildWithSteps } from '../useBuildWithSteps'; +import { useProjectSlugFromEntity } from '../useProjectSlugFromEntity'; +import { JenkinsRunStatus } from '../BuildsPage/lib/Status'; +import ExternalLinkIcon from '@material-ui/icons/Launch'; + +const useStyles = makeStyles(theme => ({ + root: { + maxWidth: 720, + margin: theme.spacing(2), + }, + table: { + padding: theme.spacing(1), + }, + externalLinkIcon: { + fontSize: 'inherit', + verticalAlign: 'bottom', + }, +})); + +const Page = () => ( + + + +); + +const BuildWithStepsView = () => { + const { owner, repo } = useProjectSlugFromEntity(); + const { branch, buildNumber } = useParams(); + const classes = useStyles(); + const buildPath = `${owner}/${repo}/${branch}/${buildNumber}`; + const [{ value }] = useBuildWithSteps(buildPath); + + return ( +

+ + Jobs + Run + + + + + + + Branch + + {value?.source?.branchName} + + + + Message + + {value?.source?.displayName} + + + + Commit ID + + {value?.source?.commit?.hash} + + + + Status + + + + + + + + Author + + {value?.source?.author} + + + + Jenkins + + + + View on Jenkins{' '} + + + + + + + GitHub + + + + View on GitHub{' '} + + + + + +
+
+
+ ); +}; + +export default Page; +export { BuildWithStepsView as BuildWithSteps }; diff --git a/plugins/jenkins/src/pages/BuildWithStepsPage/index.ts b/plugins/jenkins/src/components/BuildWithStepsPage/index.ts similarity index 100% rename from plugins/jenkins/src/pages/BuildWithStepsPage/index.ts rename to plugins/jenkins/src/components/BuildWithStepsPage/index.ts diff --git a/plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx b/plugins/jenkins/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx similarity index 100% rename from plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx rename to plugins/jenkins/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx diff --git a/plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/index.ts b/plugins/jenkins/src/components/BuildWithStepsPage/lib/ActionOutput/index.ts similarity index 100% rename from plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/index.ts rename to plugins/jenkins/src/components/BuildWithStepsPage/lib/ActionOutput/index.ts diff --git a/plugins/jenkins/src/pages/BuildsPage/lib/CITable/CITable.tsx b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx similarity index 81% rename from plugins/jenkins/src/pages/BuildsPage/lib/CITable/CITable.tsx rename to plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx index 7f17107374..282cbc59b5 100644 --- a/plugins/jenkins/src/pages/BuildsPage/lib/CITable/CITable.tsx +++ b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx @@ -14,21 +14,26 @@ * limitations under the License. */ import React, { FC } from 'react'; -import { Link, Typography, Box, IconButton } from '@material-ui/core'; +import { Box, IconButton, Link, Typography } 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 { generatePath, Link as RouterLink } from 'react-router-dom'; import { Table, TableColumn } from '@backstage/core'; import { JenkinsRunStatus } from '../Status'; +import { useBuilds } from '../../../useBuilds'; +import { useProjectSlugFromEntity } from '../../../useProjectSlugFromEntity'; +import { buildRouteRef } from '../../../../plugin'; export type CITableBuildInfo = { id: string; buildName: string; - buildUrl?: string; + buildNumber: number; + buildUrl: string; source: { branchName: string; url: string; displayName: string; + author?: string; commit: { hash: string; }; @@ -105,7 +110,13 @@ const generatedColumns: TableColumn[] = [ field: 'buildName', highlight: true, render: (row: Partial) => ( - + {row.buildName} ), @@ -177,7 +188,8 @@ type Props = { pageSize: number; onChangePageSize: (pageSize: number) => void; }; -export const CITable: FC = ({ + +export const CITableView: FC = ({ projectName, loading, pageSize, @@ -191,7 +203,7 @@ export const CITable: FC = ({ return ( = ({ onClick: () => retry(), }, ]} - data={builds} + data={builds ?? []} onChangePage={onChangePage} onChangeRowsPerPage={onChangePageSize} title={ @@ -216,3 +228,18 @@ export const CITable: FC = ({ /> ); }; + +export const CITable = () => { + const { owner, repo } = useProjectSlugFromEntity(); + + const [tableProps, { setPage, retry, setPageSize }] = useBuilds(owner, repo); + + return ( + + ); +}; diff --git a/plugins/jenkins/src/pages/BuildsPage/lib/CITable/index.ts b/plugins/jenkins/src/components/BuildsPage/lib/CITable/index.ts similarity index 100% rename from plugins/jenkins/src/pages/BuildsPage/lib/CITable/index.ts rename to plugins/jenkins/src/components/BuildsPage/lib/CITable/index.ts diff --git a/plugins/jenkins/src/pages/BuildsPage/lib/Status/JenkinsRunStatus.tsx b/plugins/jenkins/src/components/BuildsPage/lib/Status/JenkinsRunStatus.tsx similarity index 100% rename from plugins/jenkins/src/pages/BuildsPage/lib/Status/JenkinsRunStatus.tsx rename to plugins/jenkins/src/components/BuildsPage/lib/Status/JenkinsRunStatus.tsx diff --git a/plugins/jenkins/src/pages/BuildsPage/lib/Status/index.ts b/plugins/jenkins/src/components/BuildsPage/lib/Status/index.ts similarity index 100% rename from plugins/jenkins/src/pages/BuildsPage/lib/Status/index.ts rename to plugins/jenkins/src/components/BuildsPage/lib/Status/index.ts diff --git a/plugins/jenkins/src/components/JenkinsPluginWidget/JenkinsLastBuildWidget.tsx b/plugins/jenkins/src/components/Cards/Cards.tsx similarity index 78% rename from plugins/jenkins/src/components/JenkinsPluginWidget/JenkinsLastBuildWidget.tsx rename to plugins/jenkins/src/components/Cards/Cards.tsx index 6731808d8e..5034effdcf 100644 --- a/plugins/jenkins/src/components/JenkinsPluginWidget/JenkinsLastBuildWidget.tsx +++ b/plugins/jenkins/src/components/Cards/Cards.tsx @@ -14,12 +14,12 @@ * limitations under the License. */ import React from 'react'; -import { Entity } from '@backstage/catalog-model'; import { Link, Theme, makeStyles, LinearProgress } from '@material-ui/core'; import { InfoCard, StructuredMetadataTable } from '@backstage/core'; import ExternalLinkIcon from '@material-ui/icons/Launch'; -import { useBuilds } from '../../state'; -import { JenkinsRunStatus } from '../../pages/BuildsPage/lib/Status'; +import { useBuilds } from '../useBuilds'; +import { JenkinsRunStatus } from '../BuildsPage/lib/Status'; +import { useProjectSlugFromEntity } from '../useProjectSlugFromEntity'; const useStyles = makeStyles({ externalLinkIcon: { @@ -38,6 +38,7 @@ const WidgetContent = ({ }) => { const classes = useStyles(); if (loading || !lastRun) return ; + return ( { - const [owner, repo] = ( - entity?.metadata.annotations?.['jenkins.io/github-folder'] ?? '/' - ).split('/'); - const [{ loading, value }] = useBuilds(owner, repo, branch); - - const lastRun = value ?? {}; - +export const LatestRunCard = ({ branch = 'master' }: { branch: string }) => { + const { owner, repo } = useProjectSlugFromEntity(); + const [{ builds, loading }] = useBuilds(owner, repo, branch); + const lastRun = builds ?? {}; return ( diff --git a/plugins/jenkins/src/components/PluginHeader/index.ts b/plugins/jenkins/src/components/Cards/index.ts similarity index 93% rename from plugins/jenkins/src/components/PluginHeader/index.ts rename to plugins/jenkins/src/components/Cards/index.ts index 4de972f6f2..dace09e1a9 100644 --- a/plugins/jenkins/src/components/PluginHeader/index.ts +++ b/plugins/jenkins/src/components/Cards/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './PluginHeader'; +export { LatestRunCard } from './Cards'; diff --git a/plugins/jenkins/src/components/Layout/Layout.tsx b/plugins/jenkins/src/components/Layout/Layout.tsx deleted file mode 100644 index c6000437a3..0000000000 --- a/plugins/jenkins/src/components/Layout/Layout.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { Header, Page, pageTheme, HeaderLabel } from '@backstage/core'; - -export const Layout: React.FC = ({ children }) => { - return ( - -
- - -
- {children} -
- ); -}; diff --git a/plugins/jenkins/src/components/Layout/index.ts b/plugins/jenkins/src/components/Layout/index.ts deleted file mode 100644 index 236fc98851..0000000000 --- a/plugins/jenkins/src/components/Layout/index.ts +++ /dev/null @@ -1,16 +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 * from './Layout'; diff --git a/plugins/jenkins/src/components/PluginHeader/PluginHeader.tsx b/plugins/jenkins/src/components/PluginHeader/PluginHeader.tsx deleted file mode 100644 index 501649e49a..0000000000 --- a/plugins/jenkins/src/components/PluginHeader/PluginHeader.tsx +++ /dev/null @@ -1,36 +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 React from 'react'; -import { ContentHeader, SupportButton } from '@backstage/core'; -import { Box, Typography } from '@material-ui/core'; - -export type Props = { title?: string }; -export const PluginHeader = ({ title = 'Jenkins' }) => { - return ( - ( - - {title} - - )} - > - - This plugin allows you to view and interact with your builds in Jenkins. - - - ); -}; diff --git a/plugins/jenkins/src/components/Router.tsx b/plugins/jenkins/src/components/Router.tsx new file mode 100644 index 0000000000..56df5456bc --- /dev/null +++ b/plugins/jenkins/src/components/Router.tsx @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { Route, Routes } from 'react-router'; +import { buildRouteRef, rootRouteRef } from '../plugin'; +import { DetailedViewPage } from './BuildWithStepsPage/'; +import { JENKINS_ANNOTATION } from '../constants'; +import { Entity } from '@backstage/catalog-model'; +import { WarningPanel } from '@backstage/core'; +import { CITable } from './BuildsPage/lib/CITable'; + +export const isPluginApplicableToEntity = (entity: Entity) => + Boolean(entity.metadata.annotations?.[JENKINS_ANNOTATION]) && + entity.metadata.annotations?.[JENKINS_ANNOTATION] !== ''; + +export const Router = ({ entity }: { entity: Entity }) => { + return !isPluginApplicableToEntity(entity) ? ( + +
entity.metadata.annotations['{JENKINS_ANNOTATION}']
+ key is missing on the entity. +
+ ) : ( + + } /> + } /> + + ); +}; diff --git a/plugins/jenkins/src/state/useAsyncPolling.ts b/plugins/jenkins/src/components/useAsyncPolling.ts similarity index 100% rename from plugins/jenkins/src/state/useAsyncPolling.ts rename to plugins/jenkins/src/components/useAsyncPolling.ts diff --git a/plugins/jenkins/src/state/useBuildWithSteps.ts b/plugins/jenkins/src/components/useBuildWithSteps.ts similarity index 97% rename from plugins/jenkins/src/state/useBuildWithSteps.ts rename to plugins/jenkins/src/components/useBuildWithSteps.ts index 617b2b1f4f..86d8162612 100644 --- a/plugins/jenkins/src/state/useBuildWithSteps.ts +++ b/plugins/jenkins/src/components/useBuildWithSteps.ts @@ -16,7 +16,7 @@ import { errorApiRef, useApi } from '@backstage/core'; import { useCallback } from 'react'; import { useAsyncRetry } from 'react-use'; -import { jenkinsApiRef } from '../api/index'; +import { jenkinsApiRef } from '../api'; import { useAsyncPolling } from './useAsyncPolling'; const INTERVAL_AMOUNT = 1500; diff --git a/plugins/jenkins/src/state/useBuilds.ts b/plugins/jenkins/src/components/useBuilds.ts similarity index 92% rename from plugins/jenkins/src/state/useBuilds.ts rename to plugins/jenkins/src/components/useBuilds.ts index deb3125637..62e3719dbd 100644 --- a/plugins/jenkins/src/state/useBuilds.ts +++ b/plugins/jenkins/src/components/useBuilds.ts @@ -56,8 +56,9 @@ export function useBuilds(owner: string, repo: string, branch?: string) { }); }, [repo, getBuilds]); - const { loading, value, retry } = useAsyncRetry( - () => getBuilds().then(builds => builds ?? [], restartBuild), + const { loading, value: builds, retry } = useAsyncRetry( + () => + getBuilds().then(retrievedBuilds => retrievedBuilds ?? [], restartBuild), [page, pageSize, getBuilds], ); @@ -67,12 +68,12 @@ export function useBuilds(owner: string, repo: string, branch?: string) { page, pageSize, loading, - value, + builds, projectName, total, }, { - getBuilds, + builds, setPage, setPageSize, restartBuild, diff --git a/plugins/jenkins/src/components/JenkinsPluginWidget/JenkinsBuildsWidget.tsx b/plugins/jenkins/src/components/useProjectSlugFromEntity.ts similarity index 66% rename from plugins/jenkins/src/components/JenkinsPluginWidget/JenkinsBuildsWidget.tsx rename to plugins/jenkins/src/components/useProjectSlugFromEntity.ts index 12eaf5653b..1dca918990 100644 --- a/plugins/jenkins/src/components/JenkinsPluginWidget/JenkinsBuildsWidget.tsx +++ b/plugins/jenkins/src/components/useProjectSlugFromEntity.ts @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { useEntity } from '@backstage/plugin-catalog'; +import { JENKINS_ANNOTATION } from '../constants'; -import React from 'react'; -import { Builds } from '../../pages/BuildsPage/lib/Builds'; -import { Entity } from '@backstage/catalog-model'; +export const useProjectSlugFromEntity = () => { + const { entity } = useEntity(); -export const JenkinsBuildsWidget = ({ entity }: { entity: Entity }) => { const [owner, repo] = ( - entity?.metadata.annotations?.['jenkins.io/github-folder'] ?? '/' + entity.metadata.annotations?.[JENKINS_ANNOTATION] ?? '' ).split('/'); - - return ; + return { owner, repo }; }; diff --git a/plugins/jenkins/src/pages/BuildsPage/lib/Builds/index.ts b/plugins/jenkins/src/constants.ts similarity index 90% rename from plugins/jenkins/src/pages/BuildsPage/lib/Builds/index.ts rename to plugins/jenkins/src/constants.ts index e91a9496b7..fe8980de73 100644 --- a/plugins/jenkins/src/pages/BuildsPage/lib/Builds/index.ts +++ b/plugins/jenkins/src/constants.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { Builds } from './Builds'; +export const JENKINS_ANNOTATION = 'jenkins.io/github-folder'; diff --git a/plugins/jenkins/src/index.ts b/plugins/jenkins/src/index.ts index c7f2d8dc28..30fa0c70a5 100644 --- a/plugins/jenkins/src/index.ts +++ b/plugins/jenkins/src/index.ts @@ -14,5 +14,8 @@ * limitations under the License. */ -export { plugin, JenkinsBuildsWidget, JenkinsLastBuildWidget } from './plugin'; +export { plugin } from './plugin'; +export { LatestRunCard } from './components/Cards'; +export { Router, isPluginApplicableToEntity } from './components/Router'; +export { JENKINS_ANNOTATION } from './constants'; export * from './api'; diff --git a/plugins/jenkins/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/jenkins/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx deleted file mode 100644 index ab4fc3f34f..0000000000 --- a/plugins/jenkins/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx +++ /dev/null @@ -1,164 +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 React, { FC, useEffect } from 'react'; -import { useSearchParams } from 'react-router-dom'; -import { Content, InfoCard, Progress } from '@backstage/core'; -import { Grid, Box, Link, IconButton } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import { PluginHeader } from '../../components/PluginHeader'; -import { ActionOutput } from './lib/ActionOutput/ActionOutput'; -import { Layout } from '../../components/Layout'; -import LaunchIcon from '@material-ui/icons/Launch'; -import GitHubIcon from '@material-ui/icons/GitHub'; -import { useBuildWithSteps } from '../../state/useBuildWithSteps'; - -const IconLink = IconButton as typeof Link; -const BuildName: FC<{ build?: any }> = ({ build }) => ( - - {build?.buildName} - - {/* TODO use Jenkins logo*/} - - - - - -); -const useStyles = makeStyles(theme => ({ - neutral: {}, - failed: { - position: 'relative', - '&:after': { - pointerEvents: 'none', - content: '""', - position: 'absolute', - top: 0, - right: 0, - left: 0, - bottom: 0, - boxShadow: `inset 4px 0px 0px ${theme.palette.error.main}`, - }, - }, - running: { - position: 'relative', - '&:after': { - pointerEvents: 'none', - content: '""', - position: 'absolute', - top: 0, - right: 0, - left: 0, - bottom: 0, - boxShadow: `inset 4px 0px 0px ${theme.palette.info.main}`, - }, - }, - cardContent: { - backgroundColor: theme.palette.background.default, - }, - success: { - position: 'relative', - '&:after': { - pointerEvents: 'none', - content: '""', - position: 'absolute', - top: 0, - right: 0, - left: 0, - bottom: 0, - boxShadow: `inset 4px 0px 0px ${theme.palette.success.main}`, - }, - }, -})); - -const pickClassName = ( - classes: ReturnType, - build: any = {}, -) => { - if (build.result === 'UNSTABLE') return classes.failed; - if (build.result === 'FAILURE') return classes.failed; - if (build.building) return classes.running; - if (build.status === 'SUCCESS') return classes.success; - - return classes.neutral; -}; - -const Page = () => ( - - - - - -); - -const BuildWithStepsView = () => { - const [searchParams] = useSearchParams(); - const buildPath = searchParams.get('url') || ''; - const classes = useStyles(); - const [{ loading, value }, { startPolling, stopPolling }] = useBuildWithSteps( - buildPath, - ); - - useEffect(() => { - startPolling(); - return () => stopPolling(); - }, [buildPath, startPolling, stopPolling]); - return ( - <> - - - - - } - cardClassName={classes.cardContent} - > - {loading ? : } - - - - - ); -}; - -const BuildsList: FC<{ build?: any }> = ({ build }) => ( - - {build && - build.steps && - build.steps.map(({ name, actions }: { name: string; actions: any[] }) => ( - - ))} - -); - -const ActionsList: FC<{ actions: any[]; name: string }> = ({ actions }) => { - const classes = useStyles(); - return ( - <> - {actions.map((action: any) => ( - - ))} - - ); -}; - -export default Page; -export { BuildWithStepsView as BuildWithSteps }; diff --git a/plugins/jenkins/src/pages/BuildsPage/lib/Builds/Builds.tsx b/plugins/jenkins/src/pages/BuildsPage/lib/Builds/Builds.tsx deleted file mode 100644 index 79f081c82d..0000000000 --- a/plugins/jenkins/src/pages/BuildsPage/lib/Builds/Builds.tsx +++ /dev/null @@ -1,38 +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 React from 'react'; -import { CITable } from '../CITable'; -import { useBuilds } from '../../../../state'; - -export const Builds = ({ owner, repo }: { owner: string; repo: string }) => { - const [ - { total, loading, value, projectName, page, pageSize }, - { setPage, retry, setPageSize }, - ] = useBuilds(owner, repo); - return ( - - ); -}; diff --git a/plugins/jenkins/src/plugin.ts b/plugins/jenkins/src/plugin.ts index 4d1af322c4..8282f6b7f9 100644 --- a/plugins/jenkins/src/plugin.ts +++ b/plugins/jenkins/src/plugin.ts @@ -20,11 +20,15 @@ import { createApiFactory, configApiRef, } from '@backstage/core'; -import { DetailedViewPage } from './pages/BuildWithStepsPage'; import { jenkinsApiRef, JenkinsApi } from './api'; +export const rootRouteRef = createRouteRef({ + path: '', + title: 'Jenkins', +}); + export const buildRouteRef = createRouteRef({ - path: '/jenkins/job', + path: 'run/:branch/:buildNumber', title: 'Jenkins run', }); @@ -40,10 +44,4 @@ export const plugin = createPlugin({ ), }), ], - register({ router }) { - router.addRoute(buildRouteRef, DetailedViewPage); - }, }); - -export { JenkinsBuildsWidget } from './components/JenkinsPluginWidget/JenkinsBuildsWidget'; -export { JenkinsLastBuildWidget } from './components/JenkinsPluginWidget/JenkinsLastBuildWidget'; diff --git a/plugins/jenkins/src/state/index.ts b/plugins/jenkins/src/state/index.ts deleted file mode 100644 index d21a380c2a..0000000000 --- a/plugins/jenkins/src/state/index.ts +++ /dev/null @@ -1,17 +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 * from './useBuilds'; -export * from './useBuildWithSteps'; From 11cddc25aa6c51af574ca87a99aba4b0cd285d6c Mon Sep 17 00:00:00 2001 From: Fabian Chong Date: Thu, 10 Sep 2020 13:14:32 +0800 Subject: [PATCH 075/219] rename packageName --- .../commands/create-plugin/createPlugin.ts | 34 +++++++------------ .../templates/default-plugin/package.json.hbs | 4 +-- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index fe23acc924..001a86ba6f 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -87,11 +87,9 @@ export const addExportStatement = async ( export async function addPluginDependencyToApp( rootDir: string, - pluginName: string, versionStr: string, - scopeNameWithSlash: string, + pluginPackage: string, ) { - const pluginPackage = `${scopeNameWithSlash}plugin-${pluginName}`; const packageFilePath = 'packages/app/package.json'; const packageFile = resolvePath(rootDir, packageFilePath); @@ -121,9 +119,8 @@ export async function addPluginDependencyToApp( export async function addPluginToApp( rootDir: string, pluginName: string, - scopeNameWithSlash: string, + pluginPackage: string, ) { - const pluginPackage = `${scopeNameWithSlash}plugin-${pluginName}`; const pluginNameCapitalized = pluginName .split('-') .map(name => capitalize(name)) @@ -183,10 +180,6 @@ export async function movePlugin( export default async (cmd: Command) => { const codeownersPath = await getCodeownersFilePath(paths.targetRoot); - const scopeName = cmd.scope ? `@${cmd.scope.replace(/^@/, '')}` : ''; - const scopeNameWithSlash = cmd.scope ? `${scopeName}/` : ''; - const privatePackage = cmd.private === false ? false : true; - const registryURL = cmd.npmRegistry; const questions: Question[] = [ { @@ -231,6 +224,13 @@ export default async (cmd: Command) => { } const answers: Answers = await inquirer.prompt(questions); + const packageName = cmd.scope + ? `@${cmd.scope.replace(/^@/, '')}/plugin-${answers.id}` + : `plugin-${answers.id}`; + const scopeName = cmd.scope ? `@${cmd.scope.replace(/^@/, '')}` : ''; + const registryURL = (cmd.npmRegistry && + cmd.scope)`"${scopeName}:registry": "${cmd.npmRegistry}",`; + const privatePackage = cmd.private === false ? false : true; const appPackage = paths.resolveTargetRoot('packages/app'); const templateDir = paths.resolveOwn('templates/default-plugin'); @@ -257,6 +257,7 @@ export default async (cmd: Command) => { version, backstageVersion, scopeName, + packageName, privatePackage, registryURL, }); @@ -269,15 +270,10 @@ export default async (cmd: Command) => { if (await fs.pathExists(appPackage)) { Task.section('Adding plugin as dependency in app'); - await addPluginDependencyToApp( - paths.targetRoot, - answers.id, - version, - scopeNameWithSlash, - ); + await addPluginDependencyToApp(paths.targetRoot, version, packageName); Task.section('Import plugin in app'); - await addPluginToApp(paths.targetRoot, answers.id, scopeNameWithSlash); + await addPluginToApp(paths.targetRoot, answers.id, packageName); } if (ownerIds && ownerIds.length) { @@ -289,11 +285,7 @@ export default async (cmd: Command) => { } Task.log(); - Task.log( - `🥇 Successfully created ${chalk.cyan( - `${scopeNameWithSlash}plugin-${answers.id}`, - )}`, - ); + Task.log(`🥇 Successfully created ${chalk.cyan(`${packageName}`)}`); Task.log(); Task.exit(); } catch (error) { diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index 1180d67ea8..e6f9209997 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -1,12 +1,12 @@ { - "name": "{{#if scopeName}}{{scopeName}}/{{/if}}plugin-{{id}}", + "name": "{{packageName}}", "version": "{{version}}", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "private": {{privatePackage}}, "publishConfig": { - {{#if registryURL}}"{{scopeName}}:registry":"{{registryURL}}",{{/if}} + {{registryURL}} "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" From 816f158623a86cac23da8387aebc31cff8e1b3cc Mon Sep 17 00:00:00 2001 From: Fabian Chong Date: Thu, 10 Sep 2020 14:02:26 +0800 Subject: [PATCH 076/219] fix registryURL --- packages/cli/src/commands/create-plugin/createPlugin.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index 001a86ba6f..561fa36f11 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -228,8 +228,10 @@ export default async (cmd: Command) => { ? `@${cmd.scope.replace(/^@/, '')}/plugin-${answers.id}` : `plugin-${answers.id}`; const scopeName = cmd.scope ? `@${cmd.scope.replace(/^@/, '')}` : ''; - const registryURL = (cmd.npmRegistry && - cmd.scope)`"${scopeName}:registry": "${cmd.npmRegistry}",`; + const registryURL = + cmd.npmRegistry && cmd.scope + ? `"${scopeName}:registry": "${cmd.npmRegistry}",` + : null; const privatePackage = cmd.private === false ? false : true; const appPackage = paths.resolveTargetRoot('packages/app'); From 9cb2cfe2eec407af162b7abc674bf00230c0ddba Mon Sep 17 00:00:00 2001 From: Fabian Chong Date: Thu, 10 Sep 2020 17:23:00 +0800 Subject: [PATCH 077/219] detect monorepo --- packages/cli/src/commands/create-plugin/createPlugin.ts | 4 +++- packages/cli/src/lib/tasks.ts | 2 +- packages/create-app/templates/default-app/package.json.hbs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index 561fa36f11..48bc9025d9 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -237,9 +237,11 @@ export default async (cmd: Command) => { const appPackage = paths.resolveTargetRoot('packages/app'); const templateDir = paths.resolveOwn('templates/default-plugin'); const tempDir = resolvePath(os.tmpdir(), answers.id); - const pluginDir = (await fs.pathExists(paths.resolveTargetRoot('plugins'))) + const pluginDir = (await fs.pathExists(paths.resolveTargetRoot('lerna.json'))) ? paths.resolveTargetRoot('plugins', answers.id) : paths.resolveTargetRoot(answers.id); + console.log(await paths.resolveTargetRoot('lerna.json')); + console.log(await pluginDir); const ownerIds = parseOwnerIds(answers.owner); const version = backstageVersion; diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index 0573b8fda8..34f796165b 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -74,7 +74,7 @@ export async function templatingTask( throw new Error(`Failed to read template directory: ${error.message}`); }); - const isMonoRepo = paths.resolveTargetRoot('lerna.json'); + const isMonoRepo = await fs.pathExists(paths.resolveTargetRoot('lerna.json')); for (const file of files) { const destinationFile = file.replace(templateDir, destinationDir); diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index b95b5bbc5b..19e3891551 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -16,7 +16,7 @@ "test:all": "lerna run test -- --coverage", "lint": "lerna run lint --since origin/master --", "lint:all": "lerna run lint --", - "create-plugin": "backstage-cli create-plugin", + "create-plugin": "backstage-cli create-plugin --scope backstage --no-private", "remove-plugin": "backstage-cli remove-plugin" }, "workspaces": { From 1db225a02f630ecd3d6ee3a9131247fcfff386b5 Mon Sep 17 00:00:00 2001 From: Fabian Chong Date: Thu, 10 Sep 2020 18:09:09 +0800 Subject: [PATCH 078/219] keep addPluginDependencyToApp argument consistent --- packages/cli/src/commands/create-plugin/createPlugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index 48bc9025d9..398009ca8f 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -87,8 +87,8 @@ export const addExportStatement = async ( export async function addPluginDependencyToApp( rootDir: string, - versionStr: string, pluginPackage: string, + versionStr: string, ) { const packageFilePath = 'packages/app/package.json'; const packageFile = resolvePath(rootDir, packageFilePath); @@ -274,7 +274,7 @@ export default async (cmd: Command) => { if (await fs.pathExists(appPackage)) { Task.section('Adding plugin as dependency in app'); - await addPluginDependencyToApp(paths.targetRoot, version, packageName); + await addPluginDependencyToApp(paths.targetRoot, packageName, version); Task.section('Import plugin in app'); await addPluginToApp(paths.targetRoot, answers.id, packageName); From 4ec10a0643cdfa3624a5bac95c46c0fea199519e Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Thu, 10 Sep 2020 14:41:58 +0100 Subject: [PATCH 079/219] Add ssl config to example app for postgres --- .../create-app/templates/default-app/app-config.yaml.hbs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index bdd933e810..062b9b2e60 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -30,6 +30,12 @@ backend: password: $secret: env: POSTGRES_PASSWORD + ssl: + #ca: # if you have a CA file and want to verify it you can uncomment this section + # $secret: + # file: /ca/server.crt + $secret: + env: POSTGRES_SSL_OPTIONS {{/if}} proxy: From 39a5419fd75bf75f00d7df6786072606811c2492 Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Fri, 11 Sep 2020 09:37:39 +0100 Subject: [PATCH 080/219] Rerun CI From f14d2735f81a0b7de589171fb9bb83bb2c3c7163 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 11 Sep 2020 11:06:25 +0200 Subject: [PATCH 081/219] feat: extend API entity by lifecycle and owner --- .../software-catalog/descriptor-format.md | 37 +++++++++++++++++++ .../examples/hello-world-api.yaml | 2 + .../catalog-model/examples/petstore-api.yaml | 5 +++ .../examples/streetlights-api.yaml | 4 +- .../src/kinds/ApiEntityV1alpha1.test.ts | 32 ++++++++++++++++ .../src/kinds/ApiEntityV1alpha1.ts | 4 ++ 6 files changed, 83 insertions(+), 1 deletion(-) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index fa4b8c23da..1ec35d10ce 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -483,6 +483,8 @@ metadata: description: Retrieve artist details spec: type: openapi + lifecycle: production + owner: artist-relations@example.com definition: | openapi: "3.0.0" info: @@ -527,6 +529,41 @@ The current set of well-known and common values for this field is: [Protocol Buffers](https://developers.google.com/protocol-buffers) to use with [gRPC](https://grpc.io/). +### `spec.lifecycle` [required] + +The lifecycle state of the API, e.g. `production`. This field is required. + +The software catalog accepts any lifecycle value, but an organization should +take great care to establish a proper taxonomy for these. + +The current set of well-known and common values for this field is: + +- `experimental` - an experiment or early, non-production API, signaling that + users may not prefer to consume it over other more established APIs, or that + there are low or no reliability guarantees +- `production` - an established, owned, maintained API +- `deprecated` - an API that is at the end of its lifecycle, and may disappear + at a later point in time + +### `spec.owner` [required] + +The owner of the API, e.g. `artist-relations@example.com`. This field is +required. + +In Backstage, the owner of an API is the singular entity (commonly a team) that +bears ultimate responsibility for the API, and has the authority and capability +to develop and maintain it. They will be the point of contact if something goes +wrong, or if features are to be requested. The main purpose of this field is for +display purposes in Backstage, so that people looking at catalog items can get +an understanding of to whom this API belongs. It is not to be used by automated +processes to for example assign authorization in runtime systems. There may be +others that also develop or otherwise touch the API, but there will always be +one ultimate owner. + +Apart from being a string, the software catalog leaves the format of this field +open to implementers to choose. Most commonly, it is set to the ID or email of a +group of people in an organizational structure. + ### `spec.definition` [required] The definition of the API, based on the format defined by `spec.type`. This diff --git a/packages/catalog-model/examples/hello-world-api.yaml b/packages/catalog-model/examples/hello-world-api.yaml index 298b0cb835..659c48adfb 100644 --- a/packages/catalog-model/examples/hello-world-api.yaml +++ b/packages/catalog-model/examples/hello-world-api.yaml @@ -5,6 +5,8 @@ metadata: description: Hello World example for gRPC spec: type: grpc + lifecycle: deprecated + owner: grpc@example.com definition: | // Copyright 2015 gRPC authors. // diff --git a/packages/catalog-model/examples/petstore-api.yaml b/packages/catalog-model/examples/petstore-api.yaml index 01953a4a97..c8ce576cce 100644 --- a/packages/catalog-model/examples/petstore-api.yaml +++ b/packages/catalog-model/examples/petstore-api.yaml @@ -3,8 +3,13 @@ kind: API metadata: name: petstore description: The petstore API + tags: + - store + - rest spec: type: openapi + lifecycle: experimental + owner: pets@example.com definition: | openapi: "3.0.0" info: diff --git a/packages/catalog-model/examples/streetlights-api.yaml b/packages/catalog-model/examples/streetlights-api.yaml index 4aeef993bb..d53b05fc2d 100644 --- a/packages/catalog-model/examples/streetlights-api.yaml +++ b/packages/catalog-model/examples/streetlights-api.yaml @@ -4,9 +4,11 @@ metadata: name: streetlights description: The Smartylighting Streetlights API allows you to remotely manage the city lights. tags: - - unstable + - mqtt spec: type: asyncapi + lifecycle: production + owner: streetlights@example.com definition: | asyncapi: 2.0.0 info: diff --git a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts index 48de569213..35a1c59ef8 100644 --- a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts @@ -33,6 +33,8 @@ describe('ApiV1alpha1Policy', () => { }, spec: { type: 'openapi', + lifecycle: 'production', + owner: 'me', definition: ` openapi: "3.0.0" info: @@ -109,6 +111,36 @@ components: await expect(policy.enforce(entity)).rejects.toThrow(/type/); }); + it('rejects missing lifecycle', async () => { + delete (entity as any).spec.lifecycle; + await expect(policy.enforce(entity)).rejects.toThrow(/lifecycle/); + }); + + it('rejects wrong lifecycle', async () => { + (entity as any).spec.lifecycle = 7; + await expect(policy.enforce(entity)).rejects.toThrow(/lifecycle/); + }); + + it('rejects empty lifecycle', async () => { + (entity as any).spec.lifecycle = ''; + await expect(policy.enforce(entity)).rejects.toThrow(/lifecycle/); + }); + + it('rejects missing owner', async () => { + delete (entity as any).spec.owner; + await expect(policy.enforce(entity)).rejects.toThrow(/owner/); + }); + + it('rejects wrong owner', async () => { + (entity as any).spec.owner = 7; + await expect(policy.enforce(entity)).rejects.toThrow(/owner/); + }); + + it('rejects empty owner', async () => { + (entity as any).spec.owner = ''; + await expect(policy.enforce(entity)).rejects.toThrow(/owner/); + }); + it('rejects missing definition', async () => { delete (entity as any).spec.definition; await expect(policy.enforce(entity)).rejects.toThrow(/definition/); diff --git a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts index 59e16df9a7..972f6df96d 100644 --- a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts @@ -26,6 +26,8 @@ export interface ApiEntityV1alpha1 extends Entity { kind: typeof KIND; spec: { type: string; + lifecycle: string; + owner: string; definition: string; }; } @@ -40,6 +42,8 @@ export class ApiEntityV1alpha1Policy implements EntityPolicy { spec: yup .object({ type: yup.string().required().min(1), + lifecycle: yup.string().required().min(1), + owner: yup.string().required().min(1), definition: yup.string().required().min(1), }) .required(), From c95fd141aed45d42f6954fe6744a06fa9974cda7 Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Fri, 11 Sep 2020 16:59:15 +0100 Subject: [PATCH 082/219] Remove hardcoded isOrg --- .../software-templates/installation.md | 3 -- .../techdocs/creating-and-publishing.md | 5 ---- .../__mocks__/@octokit/rest/index.ts | 3 ++ .../scaffolder/stages/publish/github.test.ts | 28 +++++++++++++++++-- .../src/scaffolder/stages/publish/github.ts | 9 ++++-- plugins/scaffolder/src/api.ts | 3 +- 6 files changed, 35 insertions(+), 16 deletions(-) diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index 50c2fd303c..eea78e3571 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -177,9 +177,6 @@ docs on creating private GitHub access tokens is available Note that the need for private GitHub access tokens will be replaced with GitHub Apps integration further down the line. -> **Right now it is only possible to scaffold repositories inside GitHub -> organizations, and not under personal accounts.** - The GitHub access token is passed along using the `GITHUB_ACCESS_TOKEN` environment variable. diff --git a/docs/features/techdocs/creating-and-publishing.md b/docs/features/techdocs/creating-and-publishing.md index 0dfa40f819..9866246c5f 100644 --- a/docs/features/techdocs/creating-and-publishing.md +++ b/docs/features/techdocs/creating-and-publishing.md @@ -30,11 +30,6 @@ the documentation template. Create an entity from the documentation template and you will get the needed setup for free. -!!! warning Currently the Backstage Software Templates are limited to create -repositories inside GitHub organizations. You also need to generate an personal -access token and use as an environment variable. Read more about this -[here](../software-templates/installation.md#runtime-dependencies). - ### Manually add documentation setup to already existing repository Prerequisities: diff --git a/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts b/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts index 85bed3a223..a302bc5931 100644 --- a/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts @@ -19,6 +19,9 @@ export const mockGithubClient = { createInOrg: jest.fn(), createForAuthenticatedUser: jest.fn(), }, + users: { + getByUsername: jest.fn(), + }, }; export class Octokit { diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts index 93493ab2cc..718c01438c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -19,11 +19,18 @@ jest.mock('nodegit'); import { Octokit } from '@octokit/rest'; import * as NodeGit from 'nodegit'; -import { OctokitResponse, ReposCreateInOrgResponseData } from '@octokit/types'; +import { + OctokitResponse, + ReposCreateInOrgResponseData, + UsersGetByUsernameResponseData, +} from '@octokit/types'; import { GithubPublisher } from './github'; const { mockGithubClient } = require('@octokit/rest') as { - mockGithubClient: { repos: jest.Mocked }; + mockGithubClient: { + repos: jest.Mocked; + users: jest.Mocked; + }; }; const { @@ -59,10 +66,14 @@ describe('GitHub Publisher', () => { clone_url: 'mockclone', }, } as OctokitResponse); + mockGithubClient.users.getByUsername.mockResolvedValue({ + data: { + type: 'Organization', + }, + } as OctokitResponse); await publisher.publish({ values: { - isOrg: true, storePath: 'blam/test', owner: 'bob', }, @@ -81,6 +92,11 @@ describe('GitHub Publisher', () => { clone_url: 'mockclone', }, } as OctokitResponse); + mockGithubClient.users.getByUsername.mockResolvedValue({ + data: { + type: 'User', + }, + } as OctokitResponse); await publisher.publish({ values: { @@ -112,6 +128,12 @@ describe('GitHub Publisher', () => { clone_url: 'mockclone', }, } as OctokitResponse); + mockGithubClient.users.getByUsername.mockResolvedValue({ + data: { + type: 'Organization', + }, + } as OctokitResponse); + it('should call init on the repo with the directory', async () => { await publisher.publish({ values, diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index 5fd64d69fc..f3306e3662 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -45,9 +45,12 @@ export class GithubPublisher implements PublisherBase { ) { const [owner, name] = values.storePath.split('/'); - const repoCreationPromise = values.isOrg - ? this.client.repos.createInOrg({ name, org: owner }) - : this.client.repos.createForAuthenticatedUser({ name }); + const user = await this.client.users.getByUsername({ username: owner }); + + const repoCreationPromise = + user.data.type === 'Organization' + ? this.client.repos.createInOrg({ name, org: owner }) + : this.client.repos.createForAuthenticatedUser({ name }); const { data } = await repoCreationPromise; diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index cc69ed7627..f58c8e68d1 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -46,8 +46,7 @@ export class ScaffolderApi { headers: { 'Content-Type': 'application/json', }, - // TODO(shmidt-i): when repo picker is implemented, take isOrg from it - body: JSON.stringify({ template, values: { ...values, isOrg: true } }), + body: JSON.stringify({ template, values: { ...values } }), }); if (response.status !== 201) { From 5c9e1da0a9d3820545c18cbaf60c06b2861869d0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 12 Sep 2020 16:06:10 +0200 Subject: [PATCH 083/219] github/workflows: remove global yarn cache from windows build --- .github/workflows/e2e-win.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/e2e-win.yml b/.github/workflows/e2e-win.yml index b4ea1e350f..5bab1b1299 100644 --- a/.github/workflows/e2e-win.yml +++ b/.github/workflows/e2e-win.yml @@ -27,25 +27,12 @@ jobs: steps: - uses: actions/checkout@v2 - # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - registry-url: https://registry.npmjs.org/ # Needed for auth - - name: find location of global yarn cache - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: cache global yarn cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - name: yarn install run: yarn install --frozen-lockfile - # End of yarn setup - run: yarn tsc - name: yarn build From 155f6d3e391eb931c4f6e8016cd5d44af83e931a Mon Sep 17 00:00:00 2001 From: Alex Landovskis Date: Sat, 12 Sep 2020 18:34:32 -0400 Subject: [PATCH 084/219] app-config.yaml: Add missing config for GithubReaderProcessor. --- app-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app-config.yaml b/app-config.yaml index 5b704f9b8e..9fdf7a28ee 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -57,6 +57,10 @@ catalog: rules: - allow: [Component, API, Group, Template, Location] processors: + github: + privateToken: + $secret: + env: GITHUB_PRIVATE_TOKEN githubApi: privateToken: $secret: From fc7b1325adbefb329962a13ab30ae222053814fd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 13 Sep 2020 21:40:28 +0200 Subject: [PATCH 085/219] CHANGELOG: update until 47800 + bump to alpha.21 --- CHANGELOG.md | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f9235172b..294c57815e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,40 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re ## Next Release +### @backstage/core + +- Introduced initial version of an inverted app/plugin relationship, where plugins export components for apps to use, instead registering themselves directly into the app. This enables more fine-grained control of plugin features, and also composition of plugins such as catalog pages with additional cards and tabs. This breaks the use of `RouteRef`s, and there will be more changes related to this in the future, but this change lays the initial foundation. See `packages/app` and followup PRs for how to update plugins for this change. [#2076](https://github.com/spotify/backstage/pull/2076) +- Switch to an automatic dependency injection mechanism for all Utility APIs, allowing plugins to ship default implementations of their APIs. See [https://backstage.io/docs/api/utility-apis](https://backstage.io/docs/api/utility-apis). [#2285](https://github.com/spotify/backstage/pull/2285) + > Collect changes for the next release below +### @backstage/cli + +- Change `backstage-cli backend:build-image` to forward all args to `docker image build`, instead of just tag. Also add `--build` flag for building all dependent packages before packaging the workspace for the docker build. [#2299](https://github.com/spotify/backstage/pull/2299) + +### @backstage/create-app + +- Change root `tsc` output dir to `dist-types`, in order to allow for standalone plugin repos. [#2278](https://github.com/spotify/backstage/pull/2278) + +## v0.1.1-alpha.21 + +- Added many more frontend plugins to the template along with the sidebar. [#1942](https://github.com/spotify/backstage/pull/1942), [#2084](https://github.com/spotify/backstage/pull/2084) + +### @backstage/core + - Material-UI: Bumped to 4.11.0, which is the version that create-app will resolve to, because we wanted to get the renaming of ExpansionPanel to Accordion into place. This gets rid of a lot of console deprecation warnings in newly scaffolded apps. +### @backstage/cli + +- Set `NODE_ENV` to `test` when running test. [#2214](https://github.com/spotify/backstage/pull/2214) + +- Fix for backend plugins names requiring to be prefixed with `@backstage` to build. [#2224](https://github.com/spotify/backstage/pull/2224) + +### @backstage/backend-common + - The backend plugin [service builder](https://github.com/spotify/backstage/blob/master/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts) no longer adds `express.json()` automatically to all routes. While convenient @@ -22,14 +49,31 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re still had to cater to that manually. We therefore decided to let plugins add `express.json()` themselves if they happen to deal with JSON data. +### @backstage/catalog-backend + +- Add rules configuration for catalog location and entity kinds. The default rules should cover most use-cases, but you may need to allow specific entity kinds when using things like Template or Group entities. [#2118](https://github.com/spotify/backstage/pull/2118) + ## v0.1.1-alpha.20 -- Includes https://github.com/spotify/backstage/pull/2097 to resolve issues with create-plugin command. +### @backstage/cli + +- Use config files according to `NODE_ENV` when serving and building frontend packages. [#2077](https://github.com/spotify/backstage/pull/2077) + +- Pin `rollup-plugin-dts` to avoid a later broken version. [#2097](https://github.com/spotify/backstage/pull/2097) ## v0.1.1-alpha.19 +### @backstage/backend-common + +- Allow listen host and port to be configured separately, in order to support PORT environment variables. [#1950](https://github.com/spotify/backstage/pull/1950) + +### @backstage/core + +- Added new `DiscoveryApi` for discovering backend endpoint in the frontend, and use in most plugins. See [packages/app/src/apis.ts](https://github.com/spotify/backstage/blob/master/packages/app/src/apis.ts) for how to register in your app. [#2074](https://github.com/spotify/backstage/pull/2074) + ### @backstage/create-app +- Added catalog and scaffolder frontend plugins to the template along with the sidebar. [#1942](https://github.com/spotify/backstage/pull/1942), [#2084](https://github.com/spotify/backstage/pull/2084) - Many plugins have been added to the catalog and will for now be required to be added to separate apps as well. This will be solved as [#1536](https://github.com/spotify/backstage/issues/1536) gets sorted out, but for now you may need to install some plugins just to get pages to work. ### @backstage/catalog-backend From a4038cd646eb5dba0618f31b7b5d503eb2ea2e55 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sun, 13 Sep 2020 22:39:21 +0200 Subject: [PATCH 086/219] Codecov: Disable comments --- .codecov.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000000..69cb76019a --- /dev/null +++ b/.codecov.yml @@ -0,0 +1 @@ +comment: false From 595c5ce98af6b13b09cae496c2b2246e8f98f9f2 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sun, 13 Sep 2020 22:40:20 +0200 Subject: [PATCH 087/219] Codecov: Set threshold of coverage change to 2% This is 2 percent points. So, if coverage drops from 50% to 47.9%, codecov github check will fail. --- .codecov.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.codecov.yml b/.codecov.yml index 69cb76019a..1ca69c2b9f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1 +1,8 @@ comment: false + +coverage: + status: + project: + default: + threshold: 2% + target: auto From 2c68f25de642ac1ab62b8473a32e65dce44b55bd Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sun, 13 Sep 2020 22:46:51 +0200 Subject: [PATCH 088/219] Codecov: Add reference documentation links in config --- .codecov.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 1ca69c2b9f..f7bc56c7c1 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,8 +1,8 @@ -comment: false +comment: false # Ref: https://docs.codecov.io/docs/pull-request-comments coverage: status: project: default: - threshold: 2% + threshold: 2% # Ref: https://docs.codecov.io/docs/codecovyml-reference#coveragestatus target: auto From 2b5f7c5248f5615a59657c5f03f1b70efe7126d7 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sun, 13 Sep 2020 23:07:50 +0200 Subject: [PATCH 089/219] Codecov: Reset threshold to 0%. It is harmless. --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index f7bc56c7c1..bbe2ee8e93 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,5 +4,5 @@ coverage: status: project: default: - threshold: 2% # Ref: https://docs.codecov.io/docs/codecovyml-reference#coveragestatus + threshold: 0% # Ref: https://docs.codecov.io/docs/codecovyml-reference#coveragestatus target: auto From 678a9c90ded2ee3173ddfda75a8c2f1154d07632 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Sun, 13 Sep 2020 22:01:07 -0400 Subject: [PATCH 090/219] feat: add graphql api type --- .../software-catalog/descriptor-format.md | 1 + .../catalog-model/examples/swapi-graphql.yaml | 1174 +++++++++++++++++ plugins/api-docs/README.md | 5 +- plugins/api-docs/package.json | 2 + .../catalog/EntityPageApi/EntityPageApi.tsx | 8 +- .../ApiDefinitionCard/ApiDefinitionCard.tsx | 13 +- .../ApiEntityPage/ApiEntityPage.tsx | 1 + .../GraphQlDefinitionWidget.tsx | 66 + .../GraphQlDefinitionWidget/index.ts | 17 + yarn.lock | 2 +- 10 files changed, 1282 insertions(+), 7 deletions(-) create mode 100644 packages/catalog-model/examples/swapi-graphql.yaml create mode 100644 plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx create mode 100644 plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index fa4b8c23da..bf01f1972c 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -471,6 +471,7 @@ Describes the following entity kind: An API describes an interface that can be exposed by a component. The API can be defined in different formats, like [OpenAPI](https://swagger.io/specification/), [AsyncAPI](https://www.asyncapi.com/docs/specifications/latest/), +[GraphQL](https://graphql.org/learn/schema/), [gRPC](https://developers.google.com/protocol-buffers), or other formats. Descriptor files for this kind may look as follows. diff --git a/packages/catalog-model/examples/swapi-graphql.yaml b/packages/catalog-model/examples/swapi-graphql.yaml new file mode 100644 index 0000000000..152d9c0afa --- /dev/null +++ b/packages/catalog-model/examples/swapi-graphql.yaml @@ -0,0 +1,1174 @@ +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: starwars-graphql + description: SWAPI GraphQL Schema +spec: + type: graphql + definition: | + schema { + query: Root + } + + """A single film.""" + type Film implements Node { + """The title of this film.""" + title: String + + """The episode number of this film.""" + episodeID: Int + + """The opening paragraphs at the beginning of this film.""" + openingCrawl: String + + """The name of the director of this film.""" + director: String + + """The name(s) of the producer(s) of this film.""" + producers: [String] + + """The ISO 8601 date format of film release at original creator country.""" + releaseDate: String + speciesConnection(after: String, first: Int, before: String, last: Int): FilmSpeciesConnection + starshipConnection(after: String, first: Int, before: String, last: Int): FilmStarshipsConnection + vehicleConnection(after: String, first: Int, before: String, last: Int): FilmVehiclesConnection + characterConnection(after: String, first: Int, before: String, last: Int): FilmCharactersConnection + planetConnection(after: String, first: Int, before: String, last: Int): FilmPlanetsConnection + + """The ISO 8601 date format of the time that this resource was created.""" + created: String + + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + + """The ID of an object""" + id: ID! + } + + """A connection to a list of items.""" + type FilmCharactersConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [FilmCharactersEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + characters: [Person] + } + + """An edge in a connection.""" + type FilmCharactersEdge { + """The item at the end of the edge""" + node: Person + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type FilmPlanetsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [FilmPlanetsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + planets: [Planet] + } + + """An edge in a connection.""" + type FilmPlanetsEdge { + """The item at the end of the edge""" + node: Planet + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type FilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [FilmsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + + """An edge in a connection.""" + type FilmsEdge { + """The item at the end of the edge""" + node: Film + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type FilmSpeciesConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [FilmSpeciesEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + species: [Species] + } + + """An edge in a connection.""" + type FilmSpeciesEdge { + """The item at the end of the edge""" + node: Species + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type FilmStarshipsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [FilmStarshipsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + starships: [Starship] + } + + """An edge in a connection.""" + type FilmStarshipsEdge { + """The item at the end of the edge""" + node: Starship + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type FilmVehiclesConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [FilmVehiclesEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + vehicles: [Vehicle] + } + + """An edge in a connection.""" + type FilmVehiclesEdge { + """The item at the end of the edge""" + node: Vehicle + + """A cursor for use in pagination""" + cursor: String! + } + + """An object with an ID""" + interface Node { + """The id of the object.""" + id: ID! + } + + """Information about pagination in a connection.""" + type PageInfo { + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! + + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! + + """When paginating backwards, the cursor to continue.""" + startCursor: String + + """When paginating forwards, the cursor to continue.""" + endCursor: String + } + + """A connection to a list of items.""" + type PeopleConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [PeopleEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + people: [Person] + } + + """An edge in a connection.""" + type PeopleEdge { + """The item at the end of the edge""" + node: Person + + """A cursor for use in pagination""" + cursor: String! + } + + """An individual person or character within the Star Wars universe.""" + type Person implements Node { + """The name of this person.""" + name: String + + """ + The birth year of the person, using the in-universe standard of BBY or ABY - + Before the Battle of Yavin or After the Battle of Yavin. The Battle of Yavin is + a battle that occurs at the end of Star Wars episode IV: A New Hope. + """ + birthYear: String + + """ + The eye color of this person. Will be "unknown" if not known or "n/a" if the + person does not have an eye. + """ + eyeColor: String + + """ + The gender of this person. Either "Male", "Female" or "unknown", + "n/a" if the person does not have a gender. + """ + gender: String + + """ + The hair color of this person. Will be "unknown" if not known or "n/a" if the + person does not have hair. + """ + hairColor: String + + """The height of the person in centimeters.""" + height: Int + + """The mass of the person in kilograms.""" + mass: Float + + """The skin color of this person.""" + skinColor: String + + """A planet that this person was born on or inhabits.""" + homeworld: Planet + filmConnection(after: String, first: Int, before: String, last: Int): PersonFilmsConnection + + """The species that this person belongs to, or null if unknown.""" + species: Species + starshipConnection(after: String, first: Int, before: String, last: Int): PersonStarshipsConnection + vehicleConnection(after: String, first: Int, before: String, last: Int): PersonVehiclesConnection + + """The ISO 8601 date format of the time that this resource was created.""" + created: String + + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + + """The ID of an object""" + id: ID! + } + + """A connection to a list of items.""" + type PersonFilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [PersonFilmsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + + """An edge in a connection.""" + type PersonFilmsEdge { + """The item at the end of the edge""" + node: Film + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type PersonStarshipsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [PersonStarshipsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + starships: [Starship] + } + + """An edge in a connection.""" + type PersonStarshipsEdge { + """The item at the end of the edge""" + node: Starship + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type PersonVehiclesConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [PersonVehiclesEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + vehicles: [Vehicle] + } + + """An edge in a connection.""" + type PersonVehiclesEdge { + """The item at the end of the edge""" + node: Vehicle + + """A cursor for use in pagination""" + cursor: String! + } + + """ + A large mass, planet or planetoid in the Star Wars Universe, at the time of + 0 ABY. + """ + type Planet implements Node { + """The name of this planet.""" + name: String + + """The diameter of this planet in kilometers.""" + diameter: Int + + """ + The number of standard hours it takes for this planet to complete a single + rotation on its axis. + """ + rotationPeriod: Int + + """ + The number of standard days it takes for this planet to complete a single orbit + of its local star. + """ + orbitalPeriod: Int + + """ + A number denoting the gravity of this planet, where "1" is normal or 1 standard + G. "2" is twice or 2 standard Gs. "0.5" is half or 0.5 standard Gs. + """ + gravity: String + + """The average population of sentient beings inhabiting this planet.""" + population: Float + + """The climates of this planet.""" + climates: [String] + + """The terrains of this planet.""" + terrains: [String] + + """ + The percentage of the planet surface that is naturally occuring water or bodies + of water. + """ + surfaceWater: Float + residentConnection(after: String, first: Int, before: String, last: Int): PlanetResidentsConnection + filmConnection(after: String, first: Int, before: String, last: Int): PlanetFilmsConnection + + """The ISO 8601 date format of the time that this resource was created.""" + created: String + + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + + """The ID of an object""" + id: ID! + } + + """A connection to a list of items.""" + type PlanetFilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [PlanetFilmsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + + """An edge in a connection.""" + type PlanetFilmsEdge { + """The item at the end of the edge""" + node: Film + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type PlanetResidentsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [PlanetResidentsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + residents: [Person] + } + + """An edge in a connection.""" + type PlanetResidentsEdge { + """The item at the end of the edge""" + node: Person + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type PlanetsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [PlanetsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + planets: [Planet] + } + + """An edge in a connection.""" + type PlanetsEdge { + """The item at the end of the edge""" + node: Planet + + """A cursor for use in pagination""" + cursor: String! + } + + type Root { + allFilms(after: String, first: Int, before: String, last: Int): FilmsConnection + film(id: ID, filmID: ID): Film + allPeople(after: String, first: Int, before: String, last: Int): PeopleConnection + person(id: ID, personID: ID): Person + allPlanets(after: String, first: Int, before: String, last: Int): PlanetsConnection + planet(id: ID, planetID: ID): Planet + allSpecies(after: String, first: Int, before: String, last: Int): SpeciesConnection + species(id: ID, speciesID: ID): Species + allStarships(after: String, first: Int, before: String, last: Int): StarshipsConnection + starship(id: ID, starshipID: ID): Starship + allVehicles(after: String, first: Int, before: String, last: Int): VehiclesConnection + vehicle(id: ID, vehicleID: ID): Vehicle + + """Fetches an object given its ID""" + node( + """The ID of an object""" + id: ID! + ): Node + } + + """A type of person or character within the Star Wars Universe.""" + type Species implements Node { + """The name of this species.""" + name: String + + """The classification of this species, such as "mammal" or "reptile".""" + classification: String + + """The designation of this species, such as "sentient".""" + designation: String + + """The average height of this species in centimeters.""" + averageHeight: Float + + """The average lifespan of this species in years, null if unknown.""" + averageLifespan: Int + + """ + Common eye colors for this species, null if this species does not typically + have eyes. + """ + eyeColors: [String] + + """ + Common hair colors for this species, null if this species does not typically + have hair. + """ + hairColors: [String] + + """ + Common skin colors for this species, null if this species does not typically + have skin. + """ + skinColors: [String] + + """The language commonly spoken by this species.""" + language: String + + """A planet that this species originates from.""" + homeworld: Planet + personConnection(after: String, first: Int, before: String, last: Int): SpeciesPeopleConnection + filmConnection(after: String, first: Int, before: String, last: Int): SpeciesFilmsConnection + + """The ISO 8601 date format of the time that this resource was created.""" + created: String + + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + + """The ID of an object""" + id: ID! + } + + """A connection to a list of items.""" + type SpeciesConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [SpeciesEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + species: [Species] + } + + """An edge in a connection.""" + type SpeciesEdge { + """The item at the end of the edge""" + node: Species + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type SpeciesFilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [SpeciesFilmsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + + """An edge in a connection.""" + type SpeciesFilmsEdge { + """The item at the end of the edge""" + node: Film + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type SpeciesPeopleConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [SpeciesPeopleEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + people: [Person] + } + + """An edge in a connection.""" + type SpeciesPeopleEdge { + """The item at the end of the edge""" + node: Person + + """A cursor for use in pagination""" + cursor: String! + } + + """A single transport craft that has hyperdrive capability.""" + type Starship implements Node { + """The name of this starship. The common name, such as "Death Star".""" + name: String + + """ + The model or official name of this starship. Such as "T-65 X-wing" or "DS-1 + Orbital Battle Station". + """ + model: String + + """ + The class of this starship, such as "Starfighter" or "Deep Space Mobile + Battlestation" + """ + starshipClass: String + + """The manufacturers of this starship.""" + manufacturers: [String] + + """The cost of this starship new, in galactic credits.""" + costInCredits: Float + + """The length of this starship in meters.""" + length: Float + + """The number of personnel needed to run or pilot this starship.""" + crew: String + + """The number of non-essential people this starship can transport.""" + passengers: String + + """ + The maximum speed of this starship in atmosphere. null if this starship is + incapable of atmosphering flight. + """ + maxAtmospheringSpeed: Int + + """The class of this starships hyperdrive.""" + hyperdriveRating: Float + + """ + The Maximum number of Megalights this starship can travel in a standard hour. + A "Megalight" is a standard unit of distance and has never been defined before + within the Star Wars universe. This figure is only really useful for measuring + the difference in speed of starships. We can assume it is similar to AU, the + distance between our Sun (Sol) and Earth. + """ + MGLT: Int + + """The maximum number of kilograms that this starship can transport.""" + cargoCapacity: Float + + """ + The maximum length of time that this starship can provide consumables for its + entire crew without having to resupply. + """ + consumables: String + pilotConnection(after: String, first: Int, before: String, last: Int): StarshipPilotsConnection + filmConnection(after: String, first: Int, before: String, last: Int): StarshipFilmsConnection + + """The ISO 8601 date format of the time that this resource was created.""" + created: String + + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + + """The ID of an object""" + id: ID! + } + + """A connection to a list of items.""" + type StarshipFilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [StarshipFilmsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + + """An edge in a connection.""" + type StarshipFilmsEdge { + """The item at the end of the edge""" + node: Film + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type StarshipPilotsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [StarshipPilotsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + pilots: [Person] + } + + """An edge in a connection.""" + type StarshipPilotsEdge { + """The item at the end of the edge""" + node: Person + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type StarshipsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [StarshipsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + starships: [Starship] + } + + """An edge in a connection.""" + type StarshipsEdge { + """The item at the end of the edge""" + node: Starship + + """A cursor for use in pagination""" + cursor: String! + } + + """A single transport craft that does not have hyperdrive capability""" + type Vehicle implements Node { + """ + The name of this vehicle. The common name, such as "Sand Crawler" or "Speeder + bike". + """ + name: String + + """ + The model or official name of this vehicle. Such as "All-Terrain Attack + Transport". + """ + model: String + + """The class of this vehicle, such as "Wheeled" or "Repulsorcraft".""" + vehicleClass: String + + """The manufacturers of this vehicle.""" + manufacturers: [String] + + """The cost of this vehicle new, in Galactic Credits.""" + costInCredits: Float + + """The length of this vehicle in meters.""" + length: Float + + """The number of personnel needed to run or pilot this vehicle.""" + crew: String + + """The number of non-essential people this vehicle can transport.""" + passengers: String + + """The maximum speed of this vehicle in atmosphere.""" + maxAtmospheringSpeed: Int + + """The maximum number of kilograms that this vehicle can transport.""" + cargoCapacity: Float + + """ + The maximum length of time that this vehicle can provide consumables for its + entire crew without having to resupply. + """ + consumables: String + pilotConnection(after: String, first: Int, before: String, last: Int): VehiclePilotsConnection + filmConnection(after: String, first: Int, before: String, last: Int): VehicleFilmsConnection + + """The ISO 8601 date format of the time that this resource was created.""" + created: String + + """The ISO 8601 date format of the time that this resource was edited.""" + edited: String + + """The ID of an object""" + id: ID! + } + + """A connection to a list of items.""" + type VehicleFilmsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [VehicleFilmsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + films: [Film] + } + + """An edge in a connection.""" + type VehicleFilmsEdge { + """The item at the end of the edge""" + node: Film + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type VehiclePilotsConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [VehiclePilotsEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + pilots: [Person] + } + + """An edge in a connection.""" + type VehiclePilotsEdge { + """The item at the end of the edge""" + node: Person + + """A cursor for use in pagination""" + cursor: String! + } + + """A connection to a list of items.""" + type VehiclesConnection { + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """A list of edges.""" + edges: [VehiclesEdge] + + """ + A count of the total number of objects in this connection, ignoring pagination. + This allows a client to fetch the first five objects by passing "5" as the + argument to "first", then fetch the total count so it could display "5 of 83", + for example. + """ + totalCount: Int + + """ + A list of all of the objects returned in the connection. This is a convenience + field provided for quickly exploring the API; rather than querying for + "{ edges { node } }" when no edge data is needed, this field can be be used + instead. Note that when clients like Relay need to fetch the "cursor" field on + the edge to enable efficient pagination, this shortcut cannot be used, and the + full "{ edges { node } }" version should be used instead. + """ + vehicles: [Vehicle] + } + + """An edge in a connection.""" + type VehiclesEdge { + """The item at the end of the edge""" + node: Vehicle + + """A cursor for use in pagination""" + cursor: String! + } diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index db78b60373..17f354e997 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -14,8 +14,9 @@ The plugin provides a standalone list of APIs, as well as an integration into th Right now, the following API formats are supported: -- [OpenAPI](https://swagger.io/specification/) 2 & 3, -- [AsyncAPI](https://www.asyncapi.com/docs/specifications/latest/), +- [OpenAPI](https://swagger.io/specification/) 2 & 3 +- [AsyncAPI](https://www.asyncapi.com/docs/specifications/latest/) +- [GraphQL](https://graphql.org/learn/schema/) Other formats are displayed as plain text, but this can easily be extented. diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index a913316925..25e5c6b900 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -29,6 +29,8 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", + "graphiql": "^1.0.0-alpha.10", + "graphql": "^15.3.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "6.0.0-beta.0", diff --git a/plugins/api-docs/src/catalog/EntityPageApi/EntityPageApi.tsx b/plugins/api-docs/src/catalog/EntityPageApi/EntityPageApi.tsx index f26691d58c..5383fc0570 100644 --- a/plugins/api-docs/src/catalog/EntityPageApi/EntityPageApi.tsx +++ b/plugins/api-docs/src/catalog/EntityPageApi/EntityPageApi.tsx @@ -16,7 +16,7 @@ import { ComponentEntity, Entity } from '@backstage/catalog-model'; import { Progress } from '@backstage/core'; -import React, { FC } from 'react'; +import React from 'react'; import { Grid } from '@material-ui/core'; import { ApiDefinitionCard, @@ -24,7 +24,11 @@ import { useComponentApiNames, } from '../../components'; -export const EntityPageApi: FC<{ entity: Entity }> = ({ entity }) => { +type Props = { + entity: Entity; +}; + +export const EntityPageApi = ({ entity }: Props) => { const apiNames = useComponentApiNames(entity as ComponentEntity); const { apiEntities, loading } = useComponentApiEntities({ diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx index 5650b0745f..055363ae1f 100644 --- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx +++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx @@ -15,12 +15,13 @@ */ import { ApiEntity } from '@backstage/catalog-model'; -import { TabbedCard, CardTab } from '@backstage/core'; +import { CardTab, TabbedCard } from '@backstage/core'; +import { Alert } from '@material-ui/lab'; import React from 'react'; import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget'; -import { Alert } from '@material-ui/lab'; import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget'; import { AsyncApiDefinitionWidget } from '../AsyncApiDefinitionWidget'; +import { GraphQlDefinitionWidget } from '../GraphQlDefinitionWidget'; type ApiDefinitionWidget = { type: string; @@ -47,6 +48,14 @@ export function defaultDefinitionWidgets(): ApiDefinitionWidget[] { ), }, + { + type: 'graphql', + title: 'GraphQL', + rawLanguage: 'graphql', + component: definition => ( + + ), + }, ]; } diff --git a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx index aa0df3f20f..b7e9996cc0 100644 --- a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx +++ b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx @@ -34,6 +34,7 @@ import { useAsync } from 'react-use'; import { ApiDefinitionCard } from '../ApiDefinitionCard'; const REDIRECT_DELAY = 1000; + function headerProps( kind: string, namespace: string | undefined, diff --git a/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx b/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx new file mode 100644 index 0000000000..7e01df1460 --- /dev/null +++ b/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { Suspense } from 'react'; +import { buildSchema } from 'graphql'; +import { makeStyles } from '@material-ui/core/styles'; +import { Progress } from '@backstage/core'; +import { BackstageTheme } from '@backstage/theme'; + +const GraphiQL = React.lazy(() => import('graphiql')); + +const useStyles = makeStyles(() => ({ + root: { + height: '100%', + display: 'flex', + flexFlow: 'column nowrap', + }, + graphiQlWrapper: { + flex: 1, + '@global': { + '.graphiql-container': { + boxSizing: 'initial', + height: '100%', + minHeight: '600px', + flex: '1 1 auto', + }, + }, + }, +})); + +type Props = { + definition: any; +}; + +export const GraphQlDefinitionWidget = ({ definition }: Props) => { + const classes = useStyles(); + const schema = buildSchema(definition); + + return ( + }> +
+
+ Promise.resolve(null) as any} + schema={schema} + docExplorerOpen + defaultSecondaryEditorOpen={false} + /> +
+
+
+ ); +}; diff --git a/plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts b/plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts new file mode 100644 index 0000000000..b60545de15 --- /dev/null +++ b/plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { GraphQlDefinitionWidget } from './GraphQlDefinitionWidget'; diff --git a/yarn.lock b/yarn.lock index 11bb8a3fd8..38f5d6c799 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17550,7 +17550,7 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-i resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-lazylog@^4.5.2: +react-lazylog@^4.5.2, react-lazylog@^4.5.3: version "4.5.3" resolved "https://registry.npmjs.org/react-lazylog/-/react-lazylog-4.5.3.tgz#289e24995b5599e75943556ac63f5e2c04d0001e" integrity sha512-lyov32A/4BqihgXgtNXTHCajXSXkYHPlIEmV8RbYjHIMxCFSnmtdg4kDCI3vATz7dURtiFTvrw5yonHnrS+NNg== From 42ebe8bdb15f18183d98e29cabaad8053fa5aee0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2020 07:59:32 +0200 Subject: [PATCH 091/219] chore(deps-dev): bump docusaurus in /microsite (#2455) Bumps [docusaurus](https://github.com/facebook/docusaurus) from 2.0.0-alpha.63 to 2.0.0-alpha.64. - [Release notes](https://github.com/facebook/docusaurus/releases) - [Changelog](https://github.com/facebook/docusaurus/blob/master/CHANGELOG-2.x.md) - [Commits](https://github.com/facebook/docusaurus/compare/v2.0.0-alpha.63...v2.0.0-alpha.64) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- microsite/package.json | 2 +- microsite/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/microsite/package.json b/microsite/package.json index 25598c18b4..734f1e4d49 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -13,7 +13,7 @@ "rename-version": "docusaurus-rename-version" }, "devDependencies": { - "docusaurus": "^2.0.0-alpha.61", + "docusaurus": "^2.0.0-alpha.64", "js-yaml": "^3.14.0" } } diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 422959d3fd..84b5d50d60 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -2228,10 +2228,10 @@ dir-glob@2.0.0: arrify "^1.0.1" path-type "^3.0.0" -docusaurus@^2.0.0-alpha.61: - version "2.0.0-alpha.63" - resolved "https://registry.npmjs.org/docusaurus/-/docusaurus-2.0.0-alpha.63.tgz#40402d47b18c42b62e93beb78ce06e000cb88312" - integrity sha512-R19pAqcTemJMt7Qykd7ogB2i6R86vbE4/vA/l5/Uuh7xg7ixSKOVZ5M7d9uSoX5jCALOwEyp5iJdleznXUVwAw== +docusaurus@^2.0.0-alpha.64: + version "2.0.0-alpha.64" + resolved "https://registry.yarnpkg.com/docusaurus/-/docusaurus-2.0.0-alpha.64.tgz#7833960e9d338403894a27b79058aa4076a676d3" + integrity sha512-ARCx0GwAvc5qx7AHvRVZidZuoDTfaaGXzgmkU23NahU6jzO/aK2Q1bH8IKNEQ5C2JuDerQ/hHDh80N20ijk82g== dependencies: "@babel/core" "^7.9.0" "@babel/plugin-proposal-class-properties" "^7.8.3" From d6b0c80afdbc0e31d607f919e4609f5433616955 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Mon, 14 Sep 2020 08:01:24 +0200 Subject: [PATCH 092/219] fix(catalog): remove unused components (#2430) This allows to resolve some circular dependencies. --- plugins/catalog/package.json | 3 - .../components/EntityPageCi/EntityPageCi.tsx | 39 ------------ .../EntityPageOverview/EntityPageOverview.tsx | 61 ------------------- 3 files changed, 103 deletions(-) delete mode 100644 plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx delete mode 100644 plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index dca8b6adee..e1b52795d3 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -23,10 +23,7 @@ "dependencies": { "@backstage/catalog-model": "^0.1.1-alpha.21", "@backstage/core": "^0.1.1-alpha.21", - "@backstage/plugin-github-actions": "^0.1.1-alpha.21", - "@backstage/plugin-jenkins": "^0.1.1-alpha.21", "@backstage/plugin-scaffolder": "^0.1.1-alpha.21", - "@backstage/plugin-sentry": "^0.1.1-alpha.21", "@backstage/plugin-techdocs": "^0.1.1-alpha.21", "@backstage/theme": "^0.1.1-alpha.21", "@material-ui/core": "^4.11.0", diff --git a/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx b/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx deleted file mode 100644 index 0974e6bb83..0000000000 --- a/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx +++ /dev/null @@ -1,39 +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. - */ - -// TODO(shmidt-i): move to the app -import { Entity } from '@backstage/catalog-model'; -import { Content } from '@backstage/core'; -import { - LatestWorkflowsForBranchCard, - GITHUB_ACTIONS_ANNOTATION, -} from '@backstage/plugin-github-actions'; -import { Grid } from '@material-ui/core'; -import React, { FC } from 'react'; - -export const EntityPageCi: FC<{ entity: Entity }> = ({ entity }) => { - return ( - - - {entity.metadata?.annotations?.[GITHUB_ACTIONS_ANNOTATION] && ( - - - - )} - - - ); -}; diff --git a/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx b/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx deleted file mode 100644 index c43e83edaa..0000000000 --- a/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx +++ /dev/null @@ -1,61 +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. - */ - -// TODO(shmidt-i): move to the app -import { Entity } from '@backstage/catalog-model'; -import { Content } from '@backstage/core'; -import { - LatestWorkflowRunCard, - GITHUB_ACTIONS_ANNOTATION, -} from '@backstage/plugin-github-actions'; -import { - JenkinsBuildsWidget, - JenkinsLastBuildWidget, -} from '@backstage/plugin-jenkins'; -import { Grid } from '@material-ui/core'; -import React, { FC } from 'react'; -import { AboutCard } from '../AboutCard'; - -export const EntityPageOverview: FC<{ entity: Entity }> = ({ entity }) => { - return ( - - - - - - {entity.metadata?.annotations?.[ - 'backstage.io/jenkins-github-folder' - ] && ( - - - - )} - {entity.metadata?.annotations?.[ - 'backstage.io/jenkins-github-folder' - ] && ( - - - - )} - {entity.metadata?.annotations?.[GITHUB_ACTIONS_ANNOTATION] && ( - - - - )} - - - ); -}; From 7f8f8f75726e0b864e423862d3ca96a92a8d860a Mon Sep 17 00:00:00 2001 From: Fabian Chong Date: Mon, 14 Sep 2020 14:40:02 +0800 Subject: [PATCH 093/219] un-escape var --- packages/cli/templates/default-plugin/package.json.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index 120563c38a..251521e0c7 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -6,7 +6,7 @@ "license": "Apache-2.0", "private": {{privatePackage}}, "publishConfig": { - {{registryURL}} + {{{registryURL}}} "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" From 8a0adf950a235a1851bcc6d05e9607e0d2124894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 13 Sep 2020 10:37:11 +0200 Subject: [PATCH 094/219] Update mkdocs.yml etc --- microsite/sidebars.json | 1 + mkdocs.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 7b4c852f4d..5d24361215 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -42,6 +42,7 @@ "features/software-catalog/installation", "features/software-catalog/system-model", "features/software-catalog/descriptor-format", + "features/software-catalog/well-known-annotations", "features/software-catalog/extending-the-model", "features/software-catalog/external-integrations", "features/software-catalog/software-catalog-api" diff --git a/mkdocs.yml b/mkdocs.yml index 10cd7df9b2..d2a844b571 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -30,6 +30,7 @@ nav: - Overview: 'features/software-catalog/index.md' - System model: 'features/software-catalog/system-model.md' - YAML File Format: 'features/software-catalog/descriptor-format.md' + - Well-known Annotations: 'features/software-catalog/well-known-annotations.md' - Configuration: 'features/software-catalog/configuration.md' - Extending the model: 'features/software-catalog/extending-the-model.md' - External integrations: 'features/software-catalog/external-integrations.md' From f90d645461bf82f5745c262e042671c6b04cb362 Mon Sep 17 00:00:00 2001 From: Fabian Chong Date: Mon, 14 Sep 2020 15:50:18 +0800 Subject: [PATCH 095/219] fix plugin:diff --- .../cli/src/commands/create-plugin/createPlugin.ts | 10 +++++----- packages/cli/src/commands/plugin/diff.ts | 2 ++ packages/cli/templates/default-plugin/package.json.hbs | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index 398009ca8f..f022f4fe9e 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -224,7 +224,7 @@ export default async (cmd: Command) => { } const answers: Answers = await inquirer.prompt(questions); - const packageName = cmd.scope + const name = cmd.scope ? `@${cmd.scope.replace(/^@/, '')}/plugin-${answers.id}` : `plugin-${answers.id}`; const scopeName = cmd.scope ? `@${cmd.scope.replace(/^@/, '')}` : ''; @@ -261,7 +261,7 @@ export default async (cmd: Command) => { version, backstageVersion, scopeName, - packageName, + name, privatePackage, registryURL, }); @@ -274,10 +274,10 @@ export default async (cmd: Command) => { if (await fs.pathExists(appPackage)) { Task.section('Adding plugin as dependency in app'); - await addPluginDependencyToApp(paths.targetRoot, packageName, version); + await addPluginDependencyToApp(paths.targetRoot, name, version); Task.section('Import plugin in app'); - await addPluginToApp(paths.targetRoot, answers.id, packageName); + await addPluginToApp(paths.targetRoot, answers.id, name); } if (ownerIds && ownerIds.length) { @@ -289,7 +289,7 @@ export default async (cmd: Command) => { } Task.log(); - Task.log(`🥇 Successfully created ${chalk.cyan(`${packageName}`)}`); + Task.log(`🥇 Successfully created ${chalk.cyan(`${name}`)}`); Task.log(); Task.exit(); } catch (error) { diff --git a/packages/cli/src/commands/plugin/diff.ts b/packages/cli/src/commands/plugin/diff.ts index 99af093f2c..064c72c448 100644 --- a/packages/cli/src/commands/plugin/diff.ts +++ b/packages/cli/src/commands/plugin/diff.ts @@ -65,9 +65,11 @@ export default async (cmd: Command) => { const { version } = await fs.readJson(paths.resolveTargetRoot('lerna.json')); const data = await readPluginData(); + const privatePackage = false; const templateFiles = await diffTemplateFiles('default-plugin', { version, backstageVersion, + privatePackage, ...data, }); await handleAllFiles(fileHandlers, templateFiles, promptFunc); diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index 251521e0c7..ed1a56d605 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -1,5 +1,5 @@ { - "name": "{{packageName}}", + "name": "{{name}}", "version": "{{version}}", "main": "src/index.ts", "types": "src/index.ts", From 6cc089abaebec7b64a74a1966fab3d6b7785c0d4 Mon Sep 17 00:00:00 2001 From: Fabian Chong Date: Mon, 14 Sep 2020 15:56:52 +0800 Subject: [PATCH 096/219] remove console.log --- packages/cli/src/commands/create-plugin/createPlugin.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index f022f4fe9e..8c2a9eeb98 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -240,8 +240,6 @@ export default async (cmd: Command) => { const pluginDir = (await fs.pathExists(paths.resolveTargetRoot('lerna.json'))) ? paths.resolveTargetRoot('plugins', answers.id) : paths.resolveTargetRoot(answers.id); - console.log(await paths.resolveTargetRoot('lerna.json')); - console.log(await pluginDir); const ownerIds = parseOwnerIds(answers.owner); const version = backstageVersion; From 9888418d413fef92cd7b878169c93bed7ae29dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 14 Sep 2020 16:43:20 +0200 Subject: [PATCH 097/219] missing yarn.lock updates again (#2456) * missing yarn.lock updates again * yarn.lock: sync the sync Co-authored-by: Patrik Oldsberg --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 11bb8a3fd8..38f5d6c799 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17550,7 +17550,7 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-i resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-lazylog@^4.5.2: +react-lazylog@^4.5.2, react-lazylog@^4.5.3: version "4.5.3" resolved "https://registry.npmjs.org/react-lazylog/-/react-lazylog-4.5.3.tgz#289e24995b5599e75943556ac63f5e2c04d0001e" integrity sha512-lyov32A/4BqihgXgtNXTHCajXSXkYHPlIEmV8RbYjHIMxCFSnmtdg4kDCI3vATz7dURtiFTvrw5yonHnrS+NNg== From 398d7eb91ef5764ad0d56371bbb2ce537ef9a1ba Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Mon, 14 Sep 2020 16:57:37 +0200 Subject: [PATCH 098/219] TechDocs: Fix building if you already have a corrupt git tmp folder (#2457) * Fix building if you already have a corrupt git tmp folder * Logging the error we get before trying to recover --- plugins/techdocs-backend/src/helpers.ts | 33 ++++++++++++++----- .../techdocs-backend/src/service/helpers.ts | 2 +- .../src/techdocs/stages/prepare/dir.ts | 5 ++- .../src/techdocs/stages/prepare/github.ts | 2 +- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/plugins/techdocs-backend/src/helpers.ts b/plugins/techdocs-backend/src/helpers.ts index 25d7f593c2..14f4d490ba 100644 --- a/plugins/techdocs-backend/src/helpers.ts +++ b/plugins/techdocs-backend/src/helpers.ts @@ -24,6 +24,7 @@ import defaultBranch from 'default-branch'; import { Entity } from '@backstage/catalog-model'; import { InputError } from '@backstage/backend-common'; import { RemoteProtocol } from './techdocs/stages/prepare/types'; +import { Logger } from 'winston'; export type ParsedLocationAnnotation = { type: RemoteProtocol; @@ -110,6 +111,7 @@ export const getGitHubRepositoryTempFolder = async ( export const checkoutGithubRepository = async ( repoUrl: string, + logger: Logger, ): Promise => { const parsedGitLocation = parseGitUrl(repoUrl); const repositoryTmpPath = await getGitHubRepositoryTempFolder(repoUrl); @@ -119,14 +121,23 @@ export const checkoutGithubRepository = async ( const token = process.env.GITHUB_PRIVATE_TOKEN || ''; if (fs.existsSync(repositoryTmpPath)) { - const repository = await Repository.open(repositoryTmpPath); - const currentBranchName = (await repository.getCurrentBranch()).shorthand(); - await repository.fetch('origin'); - await repository.mergeBranches( - currentBranchName, - `origin/${currentBranchName}`, - ); - return repositoryTmpPath; + try { + const repository = await Repository.open(repositoryTmpPath); + const currentBranchName = ( + await repository.getCurrentBranch() + ).shorthand(); + await repository.fetch('origin'); + await repository.mergeBranches( + currentBranchName, + `origin/${currentBranchName}`, + ); + return repositoryTmpPath; + } catch (e) { + logger.info( + `Found error "${e.message}" in cached repository "${repoUrl}" when getting latest changes. Removing cached repository.`, + ); + fs.removeSync(repositoryTmpPath); + } } if (user && token) { @@ -143,8 +154,12 @@ export const checkoutGithubRepository = async ( export const getLastCommitTimestamp = async ( repositoryUrl: string, + logger: Logger, ): Promise => { - const repositoryLocation = await checkoutGithubRepository(repositoryUrl); + const repositoryLocation = await checkoutGithubRepository( + repositoryUrl, + logger, + ); const repository = await Repository.open(repositoryLocation); const commit = await repository.getReferenceCommit('HEAD'); diff --git a/plugins/techdocs-backend/src/service/helpers.ts b/plugins/techdocs-backend/src/service/helpers.ts index 0fc4f41b3d..60c76e6384 100644 --- a/plugins/techdocs-backend/src/service/helpers.ts +++ b/plugins/techdocs-backend/src/service/helpers.ts @@ -110,7 +110,7 @@ export class DocsBuilder { // Should probably be broken out and handled per type later. Doing this for now since we only support github age checks if (type === 'github') { - const lastCommit = await getLastCommitTimestamp(target); + const lastCommit = await getLastCommitTimestamp(target, this.logger); const storageTimeStamp = buildMetadataStorage.getTimestamp(); // Check if documentation source is newer than what we have diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts index a9eda943c6..5fd4abb4bd 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts @@ -43,7 +43,10 @@ export class DirectoryPreparer implements PreparerBase { switch (type) { case 'github': { const parsedGitLocation = parseGitUrl(target); - const repoLocation = await checkoutGithubRepository(target); + const repoLocation = await checkoutGithubRepository( + target, + this.logger, + ); return path.dirname( path.join(repoLocation, parsedGitLocation.filepath), diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts index 7b9fc7fbe2..03b0adcb88 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts @@ -43,7 +43,7 @@ export class GithubPreparer implements PreparerBase { } try { - const repoPath = await checkoutGithubRepository(target); + const repoPath = await checkoutGithubRepository(target, this.logger); const parsedGitLocation = parseGitUrl(target); return path.join(repoPath, parsedGitLocation.filepath); From b86d408fa7eb4bc79dd71fd6036ea4a7beea3a89 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 14 Sep 2020 17:06:29 +0200 Subject: [PATCH 099/219] plugins/newrelic: make public --- plugins/newrelic/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 9c9d609c04..38a47bc0fc 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", - "private": true, + "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", From d36f3bc5d84874372192efab33568e723374f3f6 Mon Sep 17 00:00:00 2001 From: David Tuite Date: Tue, 15 Sep 2020 05:42:13 +0100 Subject: [PATCH 100/219] Assorted scaffolder form help text improvements (#2467) * Prompt the user for a description in templates The text "Description of the component" was a little redundant since the label already says "Description". This description text hints to the user why they should bother adding an informative description. * Inform user about lowercase, url-safe constraint Create React App scaffolding fails when a name with spaces or other non-url-safe chars are provided. Instead of making the user wait for a failure, Backstage should tell them about this constraint up front. --- .../sample-templates/create-react-app/template.yaml | 4 ++-- .../sample-templates/docs-template/template.yaml | 2 +- .../sample-templates/react-ssr-template/template.yaml | 2 +- .../sample-templates/springboot-grpc-template/template.yaml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml b/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml index a377d60ccc..2c8b3b3d6d 100644 --- a/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml @@ -22,11 +22,11 @@ spec: component_id: title: Name type: string - description: Unique name of the component + description: Unique name of the component. Lowercase, URL-safe characters only. description: title: Description type: string - description: Description of the component + description: Help others understand what this website is for. use_typescript: title: Use Typescript type: boolean diff --git a/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml b/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml index 65d44d1b8a..b4bd45d163 100644 --- a/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml @@ -25,5 +25,5 @@ spec: description: title: Description type: string - description: Description of the component + description: Help others understand what these docs are about. 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 ee051de6e0..a667e6e6b6 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml @@ -24,4 +24,4 @@ spec: description: title: Description type: string - description: Description of the component + description: Help others understand what this website is for. diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml index 32b9383855..8b89298a0e 100644 --- a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml @@ -25,7 +25,7 @@ spec: description: title: Description type: string - description: Description of the component + description: Help others understand what this service does. http_port: title: Port type: integer From 111fbb9e0fcea84532e63838a9d09232478c357d Mon Sep 17 00:00:00 2001 From: Fabian Chong Date: Tue, 15 Sep 2020 13:30:18 +0800 Subject: [PATCH 101/219] fix plugin:diff --- .../commands/create-plugin/createPlugin.ts | 8 ++++---- packages/cli/src/commands/plugin/diff.ts | 20 +++++++++++++------ .../templates/default-plugin/package.json.hbs | 3 ++- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index 8c2a9eeb98..8241f68b8c 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -228,10 +228,10 @@ export default async (cmd: Command) => { ? `@${cmd.scope.replace(/^@/, '')}/plugin-${answers.id}` : `plugin-${answers.id}`; const scopeName = cmd.scope ? `@${cmd.scope.replace(/^@/, '')}` : ''; - const registryURL = + const npmRegistry = cmd.npmRegistry && cmd.scope - ? `"${scopeName}:registry": "${cmd.npmRegistry}",` - : null; + ? `"${scopeName}:registry": "${cmd.npmRegistry}"` + : ''; const privatePackage = cmd.private === false ? false : true; const appPackage = paths.resolveTargetRoot('packages/app'); @@ -261,7 +261,7 @@ export default async (cmd: Command) => { scopeName, name, privatePackage, - registryURL, + npmRegistry, }); Task.section('Moving to final location'); diff --git a/packages/cli/src/commands/plugin/diff.ts b/packages/cli/src/commands/plugin/diff.ts index 064c72c448..f5dfa670b7 100644 --- a/packages/cli/src/commands/plugin/diff.ts +++ b/packages/cli/src/commands/plugin/diff.ts @@ -30,6 +30,9 @@ import { version as backstageVersion } from '../../lib/version'; export type PluginData = { id: string; name: string; + privatePackage: string; + version: string; + npmRegistry: string; }; const fileHandlers = [ @@ -62,14 +65,9 @@ export default async (cmd: Command) => { promptFunc = yesPromptFunc; } - const { version } = await fs.readJson(paths.resolveTargetRoot('lerna.json')); - const data = await readPluginData(); - const privatePackage = false; const templateFiles = await diffTemplateFiles('default-plugin', { - version, backstageVersion, - privatePackage, ...data, }); await handleAllFiles(fileHandlers, templateFiles, promptFunc); @@ -79,9 +77,19 @@ export default async (cmd: Command) => { // Reads templating data from the existing plugin async function readPluginData(): Promise { let name: string; + let privatePackage: string; + let version: string; + let npmRegistry: string; try { const pkg = require(paths.resolveTarget('package.json')); name = pkg.name; + privatePackage = pkg.private; + version = pkg.version; + const scope = name.split('/')[0]; + if (`${scope}:registry` in pkg.publishConfig) { + const registryURL = pkg.publishConfig[`${scope}:registry`]; + npmRegistry = `"${scope}:registry" : "${registryURL}"`; + } else npmRegistry = ''; } catch (error) { throw new Error(`Failed to read target package, ${error}`); } @@ -98,5 +106,5 @@ async function readPluginData(): Promise { const id = pluginIdMatch[1]; - return { id, name }; + return { id, name, privatePackage, version, npmRegistry }; } diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index ed1a56d605..1a969f9712 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -6,7 +6,8 @@ "license": "Apache-2.0", "private": {{privatePackage}}, "publishConfig": { - {{{registryURL}}} +{{#if npmRegistry}} {{{npmRegistry}}}, +{{/if}} "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" From fdef95557017e324edb468c21c79d77c0288d2d3 Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Sat, 12 Sep 2020 21:27:26 +0100 Subject: [PATCH 102/219] scaffolder: Add support for granting access on create --- .../__mocks__/@octokit/rest/index.ts | 4 ++ .../scaffolder/stages/publish/github.test.ts | 49 ++++++++++++++++++- .../src/scaffolder/stages/publish/github.ts | 22 +++++++++ .../components/TemplatePage/TemplatePage.tsx | 7 ++- 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts b/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts index a302bc5931..e0e9efa479 100644 --- a/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/__mocks__/@octokit/rest/index.ts @@ -18,10 +18,14 @@ export const mockGithubClient = { repos: { createInOrg: jest.fn(), createForAuthenticatedUser: jest.fn(), + addCollaborator: jest.fn(), }, users: { getByUsername: jest.fn(), }, + teams: { + addOrUpdateRepoPermissionsInOrg: jest.fn(), + }, }; export class Octokit { diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts index 718c01438c..86effd0ea5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -30,6 +30,7 @@ const { mockGithubClient } = require('@octokit/rest') as { mockGithubClient: { repos: jest.Mocked; users: jest.Mocked; + teams: jest.Mocked; }; }; @@ -76,6 +77,7 @@ describe('GitHub Publisher', () => { values: { storePath: 'blam/test', owner: 'bob', + access: 'blam/team', }, directory: '/tmp/test', }); @@ -84,6 +86,15 @@ describe('GitHub Publisher', () => { org: 'blam', name: 'test', }); + expect( + mockGithubClient.teams.addOrUpdateRepoPermissionsInOrg, + ).toHaveBeenCalledWith({ + org: 'blam', + team_slug: 'team', + owner: 'blam', + repo: 'test', + permission: 'admin', + }); }); it('should use octokit to create a repo in the authed user if the organisation property is not set', async () => { @@ -102,6 +113,7 @@ describe('GitHub Publisher', () => { values: { storePath: 'blam/test', owner: 'bob', + access: 'blam', }, directory: '/tmp/test', }); @@ -111,14 +123,49 @@ describe('GitHub Publisher', () => { ).toHaveBeenCalledWith({ name: 'test', }); + expect(mockGithubClient.repos.addCollaborator).not.toHaveBeenCalled(); + }); + }); + + it('should invite other user in the authed user', async () => { + mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({ + data: { + clone_url: 'mockclone', + }, + } as OctokitResponse); + mockGithubClient.users.getByUsername.mockResolvedValue({ + data: { + type: 'User', + }, + } as OctokitResponse); + + await publisher.publish({ + values: { + storePath: 'blam/test', + owner: 'bob', + access: 'bob', + }, + directory: '/tmp/test', + }); + + expect( + mockGithubClient.repos.createForAuthenticatedUser, + ).toHaveBeenCalledWith({ + name: 'test', + }); + expect(mockGithubClient.repos.addCollaborator).toHaveBeenCalledWith({ + owner: 'blam', + repo: 'test', + username: 'bob', + permission: 'admin', }); }); describe('publish: createGitDirectory', () => { const values = { - isOrg: true, storePath: 'blam/test', owner: 'lols', + access: 'lols', }; const mockDir = '/tmp/test/dir'; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts index f3306e3662..09cb720ebe 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.ts @@ -44,6 +44,7 @@ export class GithubPublisher implements PublisherBase { values: RequiredTemplateValues & Record, ) { const [owner, name] = values.storePath.split('/'); + const access = values.access as string; const user = await this.client.users.getByUsername({ username: owner }); @@ -54,6 +55,27 @@ export class GithubPublisher implements PublisherBase { const { data } = await repoCreationPromise; + if (access.match(new RegExp(`${owner}/.+`))) { + const [, team] = access.split('/'); + await this.client.teams.addOrUpdateRepoPermissionsInOrg({ + org: owner, + team_slug: team, + owner, + repo: name, + permission: 'admin', + }); + } else { + // no need to add access if it's the person who own's the personal account + if (access !== owner) { + await this.client.repos.addCollaborator({ + owner, + repo: name, + username: access, + permission: 'admin', + }); + } + } + return data?.clone_url; } diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 1ed0504894..60f74e4cbe 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -54,7 +54,7 @@ const useTemplate = ( const OWNER_REPO_SCHEMA = { $schema: 'http://json-schema.org/draft-07/schema#' as const, - required: ['storePath', 'owner'], + required: ['storePath', 'owner', 'access'], properties: { owner: { type: 'string' as const, @@ -67,6 +67,11 @@ const OWNER_REPO_SCHEMA = { title: 'Store path', description: 'GitHub store path in org/repo format', }, + access: { + type: 'string' as const, + title: 'Access', + description: 'Who should have access, in user or owner/team format', + }, }, }; From 06b2c9ea6e92acd0ac517aad8e84b89310500728 Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Tue, 15 Sep 2020 08:42:41 +0100 Subject: [PATCH 103/219] Add docs link --- packages/create-app/templates/default-app/app-config.yaml.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 062b9b2e60..424b461392 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -30,6 +30,7 @@ backend: password: $secret: env: POSTGRES_PASSWORD + # https://node-postgres.com/features/ssl ssl: #ca: # if you have a CA file and want to verify it you can uncomment this section # $secret: From ec39c956a408a69cfc1a6e1081190db3dd1239ff Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Tue, 15 Sep 2020 08:42:54 +0100 Subject: [PATCH 104/219] Restore app-config.yaml.hbs --- .../create-app/templates/default-app/app-config.yaml.hbs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 424b461392..bdd933e810 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -30,13 +30,6 @@ backend: password: $secret: env: POSTGRES_PASSWORD - # https://node-postgres.com/features/ssl - ssl: - #ca: # if you have a CA file and want to verify it you can uncomment this section - # $secret: - # file: /ca/server.crt - $secret: - env: POSTGRES_SSL_OPTIONS {{/if}} proxy: From cccdf8d32ae36888dd3cf162dc4885e9177c6d5b Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Tue, 15 Sep 2020 08:43:59 +0100 Subject: [PATCH 105/219] Revert "Restore app-config.yaml.hbs" This reverts commit ec39c956a408a69cfc1a6e1081190db3dd1239ff. --- .../create-app/templates/default-app/app-config.yaml.hbs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index bdd933e810..424b461392 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -30,6 +30,13 @@ backend: password: $secret: env: POSTGRES_PASSWORD + # https://node-postgres.com/features/ssl + ssl: + #ca: # if you have a CA file and want to verify it you can uncomment this section + # $secret: + # file: /ca/server.crt + $secret: + env: POSTGRES_SSL_OPTIONS {{/if}} proxy: From 563d3b004dda157a5811524076bc738c0a8defb8 Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Tue, 15 Sep 2020 08:44:59 +0100 Subject: [PATCH 106/219] Update app-config.yaml.hbs --- packages/create-app/templates/default-app/app-config.yaml.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 424b461392..16a4fbaae5 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -15,6 +15,7 @@ backend: connection: ':memory:' {{/if}} {{#if dbTypePG}} + # config options: https://node-postgres.com/api/client database: client: pg connection: From 4fc24933b1e425045261a093817c2d72ead989c7 Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Tue, 15 Sep 2020 09:04:48 +0100 Subject: [PATCH 107/219] Update packages/create-app/templates/default-app/app-config.yaml.hbs --- packages/create-app/templates/default-app/app-config.yaml.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 16a4fbaae5..57091b3d3a 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -37,7 +37,7 @@ backend: # $secret: # file: /ca/server.crt $secret: - env: POSTGRES_SSL_OPTIONS + env: POSTGRES_SSL_OPTIONS # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions {{/if}} proxy: From 57ae0825d80c1688630abd61b7a070e99f2ce8bd Mon Sep 17 00:00:00 2001 From: David Tuite Date: Tue, 15 Sep 2020 14:00:23 +0100 Subject: [PATCH 108/219] Fix scaffolder catalog link (#2471) This was incorrectly linking to a relative path which ended up being something like `/create/docs-template/Component/my-component`. --- .../scaffolder/src/components/JobStatusModal/JobStatusModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx index 102e93f91d..87b70a9553 100644 --- a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx +++ b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx @@ -72,7 +72,7 @@ export const JobStatusModal = ({ {entity && (