From da53c87fb77cd265cfcdd3e4637c04024ae52252 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 8 May 2020 06:14:49 +0000 Subject: [PATCH 01/73] build(deps): bump fs-extra from 8.1.0 to 9.0.0 Bumps [fs-extra](https://github.com/jprichardson/node-fs-extra) from 8.1.0 to 9.0.0. - [Release notes](https://github.com/jprichardson/node-fs-extra/releases) - [Changelog](https://github.com/jprichardson/node-fs-extra/blob/master/CHANGELOG.md) - [Commits](https://github.com/jprichardson/node-fs-extra/compare/8.1.0...9.0.0) Signed-off-by: dependabot-preview[bot] --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ee46b09862..15f711f1b9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -43,7 +43,7 @@ "eslint-plugin-import": "^2.20.2", "eslint-plugin-monorepo": "^0.2.1", "fork-ts-checker-webpack-plugin": "^4.0.5", - "fs-extra": "^8.1.0", + "fs-extra": "^9.0.0", "handlebars": "^4.7.3", "html-webpack-plugin": "^3.2.0", "inquirer": "^7.0.4", From 198bf2d1a60a122ba5dc8152fd46782471d2f494 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 May 2020 17:18:36 +0200 Subject: [PATCH 02/73] plugins: applied diff for all plugins to enable plugin:serve --- plugins/explore/dev/index.tsx | 20 ++++++++++++++++++++ plugins/explore/package.json | 6 ++++-- plugins/explore/tsconfig.json | 2 +- plugins/inventory/dev/index.tsx | 20 ++++++++++++++++++++ plugins/inventory/package.json | 3 ++- plugins/inventory/tsconfig.json | 2 +- plugins/lighthouse/dev/index.tsx | 20 ++++++++++++++++++++ plugins/lighthouse/package.json | 6 ++++-- plugins/lighthouse/tsconfig.json | 2 +- plugins/scaffolder/dev/index.tsx | 20 ++++++++++++++++++++ plugins/scaffolder/package.json | 3 ++- plugins/scaffolder/tsconfig.json | 2 +- plugins/tech-radar/dev/index.tsx | 20 ++++++++++++++++++++ plugins/tech-radar/package.json | 6 ++++-- plugins/tech-radar/tsconfig.json | 2 +- plugins/welcome/dev/index.tsx | 20 ++++++++++++++++++++ plugins/welcome/package.json | 6 ++++-- plugins/welcome/tsconfig.json | 2 +- 18 files changed, 146 insertions(+), 16 deletions(-) create mode 100644 plugins/explore/dev/index.tsx create mode 100644 plugins/inventory/dev/index.tsx create mode 100644 plugins/lighthouse/dev/index.tsx create mode 100644 plugins/scaffolder/dev/index.tsx create mode 100644 plugins/tech-radar/dev/index.tsx create mode 100644 plugins/welcome/dev/index.tsx diff --git a/plugins/explore/dev/index.tsx b/plugins/explore/dev/index.tsx new file mode 100644 index 0000000000..812a5585d4 --- /dev/null +++ b/plugins/explore/dev/index.tsx @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp().registerPlugin(plugin).render(); diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 9adbee7b0f..c7a377c589 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -10,7 +10,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "start": "backstage-cli plugin:serve" }, "dependencies": { "@backstage/core": "^0.1.1-alpha.4", @@ -32,7 +33,8 @@ "@types/jest": "^24.0.0", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3" + "jest-fetch-mock": "^3.0.3", + "@backstage/dev-utils": "^0.1.1-alpha.4" }, "files": [ "dist" diff --git a/plugins/explore/tsconfig.json b/plugins/explore/tsconfig.json index 5a3931ffce..b663b01fa2 100644 --- a/plugins/explore/tsconfig.json +++ b/plugins/explore/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": {} } diff --git a/plugins/inventory/dev/index.tsx b/plugins/inventory/dev/index.tsx new file mode 100644 index 0000000000..812a5585d4 --- /dev/null +++ b/plugins/inventory/dev/index.tsx @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp().registerPlugin(plugin).render(); diff --git a/plugins/inventory/package.json b/plugins/inventory/package.json index ae871eb0bf..c5221fba4c 100644 --- a/plugins/inventory/package.json +++ b/plugins/inventory/package.json @@ -31,7 +31,8 @@ "@types/jest": "^24.0.0", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3" + "jest-fetch-mock": "^3.0.3", + "@backstage/dev-utils": "^0.1.1-alpha.4" }, "files": [ "dist" diff --git a/plugins/inventory/tsconfig.json b/plugins/inventory/tsconfig.json index 5a3931ffce..b663b01fa2 100644 --- a/plugins/inventory/tsconfig.json +++ b/plugins/inventory/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": {} } diff --git a/plugins/lighthouse/dev/index.tsx b/plugins/lighthouse/dev/index.tsx new file mode 100644 index 0000000000..812a5585d4 --- /dev/null +++ b/plugins/lighthouse/dev/index.tsx @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp().registerPlugin(plugin).render(); diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 50ad4c758c..22073de3a1 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -10,7 +10,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "start": "backstage-cli plugin:serve" }, "dependencies": { "@backstage/core": "^0.1.1-alpha.4", @@ -33,7 +34,8 @@ "@types/jest": "^24.0.0", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3" + "jest-fetch-mock": "^3.0.3", + "@backstage/dev-utils": "^0.1.1-alpha.4" }, "files": [ "dist" diff --git a/plugins/lighthouse/tsconfig.json b/plugins/lighthouse/tsconfig.json index 5a3931ffce..b663b01fa2 100644 --- a/plugins/lighthouse/tsconfig.json +++ b/plugins/lighthouse/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": {} } diff --git a/plugins/scaffolder/dev/index.tsx b/plugins/scaffolder/dev/index.tsx new file mode 100644 index 0000000000..812a5585d4 --- /dev/null +++ b/plugins/scaffolder/dev/index.tsx @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp().registerPlugin(plugin).render(); diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index fe7db2762d..26b9fcdb17 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -31,7 +31,8 @@ "@types/jest": "^24.0.0", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3" + "jest-fetch-mock": "^3.0.3", + "@backstage/dev-utils": "^0.1.1-alpha.4" }, "files": [ "dist" diff --git a/plugins/scaffolder/tsconfig.json b/plugins/scaffolder/tsconfig.json index 5a3931ffce..b663b01fa2 100644 --- a/plugins/scaffolder/tsconfig.json +++ b/plugins/scaffolder/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": {} } diff --git a/plugins/tech-radar/dev/index.tsx b/plugins/tech-radar/dev/index.tsx new file mode 100644 index 0000000000..812a5585d4 --- /dev/null +++ b/plugins/tech-radar/dev/index.tsx @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp().registerPlugin(plugin).render(); diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index ef1de13671..aaa3aaf7c2 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -10,7 +10,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "start": "backstage-cli plugin:serve" }, "dependencies": { "@backstage/core": "^0.1.1-alpha.4", @@ -36,7 +37,8 @@ "@types/jest": "^24.0.0", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3" + "jest-fetch-mock": "^3.0.3", + "@backstage/dev-utils": "^0.1.1-alpha.4" }, "files": [ "dist" diff --git a/plugins/tech-radar/tsconfig.json b/plugins/tech-radar/tsconfig.json index 5a3931ffce..b663b01fa2 100644 --- a/plugins/tech-radar/tsconfig.json +++ b/plugins/tech-radar/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": {} } diff --git a/plugins/welcome/dev/index.tsx b/plugins/welcome/dev/index.tsx new file mode 100644 index 0000000000..812a5585d4 --- /dev/null +++ b/plugins/welcome/dev/index.tsx @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp().registerPlugin(plugin).render(); diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 47ff206342..4898d6b07d 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -10,7 +10,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "start": "backstage-cli plugin:serve" }, "dependencies": { "@backstage/core": "^0.1.1-alpha.4", @@ -31,7 +32,8 @@ "@types/jest": "^24.0.0", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3" + "jest-fetch-mock": "^3.0.3", + "@backstage/dev-utils": "^0.1.1-alpha.4" }, "files": [ "dist" diff --git a/plugins/welcome/tsconfig.json b/plugins/welcome/tsconfig.json index 5a3931ffce..b663b01fa2 100644 --- a/plugins/welcome/tsconfig.json +++ b/plugins/welcome/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": {} } From 81cb54d52b3ffb7f91371a5f43712e8b56b0c714 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 May 2020 18:53:04 +0200 Subject: [PATCH 03/73] plugins/lighthouse: add lighthouse api to dev env --- plugins/lighthouse/dev/index.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/lighthouse/dev/index.tsx b/plugins/lighthouse/dev/index.tsx index 812a5585d4..6496fb658a 100644 --- a/plugins/lighthouse/dev/index.tsx +++ b/plugins/lighthouse/dev/index.tsx @@ -16,5 +16,13 @@ import { createDevApp } from '@backstage/dev-utils'; import { plugin } from '../src/plugin'; +import { lighthouseApiRef, LighthouseRestApi } from '../src'; -createDevApp().registerPlugin(plugin).render(); +createDevApp() + .registerPlugin(plugin) + .registerApiFactory({ + implements: lighthouseApiRef, + deps: {}, + factory: () => new LighthouseRestApi('http://localhost:3003'), + }) + .render(); From 242427be7ba55c834cab93cefad1b84d99007b29 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 May 2020 18:53:32 +0200 Subject: [PATCH 04/73] plugins/tech-radar: add lighthouse api to dev env --- plugins/tech-radar/dev/index.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/tech-radar/dev/index.tsx b/plugins/tech-radar/dev/index.tsx index 812a5585d4..ac19b63a90 100644 --- a/plugins/tech-radar/dev/index.tsx +++ b/plugins/tech-radar/dev/index.tsx @@ -16,5 +16,13 @@ import { createDevApp } from '@backstage/dev-utils'; import { plugin } from '../src/plugin'; +import { techRadarApiRef, TechRadar } from '../src'; -createDevApp().registerPlugin(plugin).render(); +createDevApp() + .registerPlugin(plugin) + .registerApiFactory({ + implements: techRadarApiRef, + deps: {}, + factory: () => new TechRadar({ width: 1500, height: 800 }), + }) + .render(); From b1400b98963c394e847df6411620fec129336380 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 11 May 2020 11:23:33 +0200 Subject: [PATCH 05/73] packages/cli: remove cypress version override from app template --- packages/cli/templates/default-app/package.json.hbs | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/cli/templates/default-app/package.json.hbs b/packages/cli/templates/default-app/package.json.hbs index 8b725c69c1..231194a54e 100644 --- a/packages/cli/templates/default-app/package.json.hbs +++ b/packages/cli/templates/default-app/package.json.hbs @@ -26,8 +26,5 @@ "@backstage/cli": "^{{version}}", "lerna": "^3.20.2", "prettier": "^1.19.1" - }, - "resolutions": { - "**/cypress": "4.2.0" } } From 4b1d80b43a122b71e51d93d32c47ed7d9831bacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 11 May 2020 13:54:27 +0200 Subject: [PATCH 06/73] Implement first knex database basics --- packages/backend/.gitignore | 1 + packages/backend/knexfile.ts | 58 ++++ packages/backend/package.json | 8 +- packages/backend/src/database.ts | 39 +++ packages/backend/src/index.ts | 8 +- .../src/migrations/20200511113813_init.ts | 45 +++ yarn.lock | 258 +++++++++++++++++- 7 files changed, 403 insertions(+), 14 deletions(-) create mode 100644 packages/backend/.gitignore create mode 100644 packages/backend/knexfile.ts create mode 100644 packages/backend/src/database.ts create mode 100644 packages/backend/src/migrations/20200511113813_init.ts diff --git a/packages/backend/.gitignore b/packages/backend/.gitignore new file mode 100644 index 0000000000..1097a1ff3c --- /dev/null +++ b/packages/backend/.gitignore @@ -0,0 +1 @@ +dev.sqlite3 diff --git a/packages/backend/knexfile.ts b/packages/backend/knexfile.ts new file mode 100644 index 0000000000..57ccbe8528 --- /dev/null +++ b/packages/backend/knexfile.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = { + development: { + client: 'sqlite3', + connection: { + filename: './dev.sqlite3', + }, + }, + + /* + staging: { + client: 'postgresql', + connection: { + database: 'my_db', + user: 'username', + password: 'password', + }, + pool: { + min: 2, + max: 10, + }, + migrations: { + tableName: 'knex_migrations', + }, + }, + + production: { + client: 'postgresql', + connection: { + database: 'my_db', + user: 'username', + password: 'password', + }, + pool: { + min: 2, + max: 10, + }, + migrations: { + tableName: 'knex_migrations', + }, + }, + */ +}; diff --git a/packages/backend/package.json b/packages/backend/package.json index 5e398a89d5..36eb09fe8a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -12,7 +12,8 @@ "start": "backstage-cli watch-deps --build -- tsc-watch --onFirstSuccess nodemon", "lint": "backstage-cli lint", "test": "backstage-cli test", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "migrate:create": "knex migrate:make -x ts" }, "dependencies": { "@backstage/backend-common": "0.1.1-alpha.4", @@ -21,7 +22,10 @@ "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.17.1", - "helmet": "^3.22.0" + "helmet": "^3.22.0", + "knex": "^0.21.1", + "sqlite3": "^4.2.0", + "winston": "^3.2.1" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.4", diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts new file mode 100644 index 0000000000..afd46b87e7 --- /dev/null +++ b/packages/backend/src/database.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import knex from 'knex'; +import path from 'path'; +import { Logger } from 'winston'; + +export async function buildDatabase(logger: Logger) { + logger.info('Connecting to database'); + const db = knex({ + client: 'sqlite3', + connection: { + filename: './dev.sqlite3', + }, + useNullAsDefault: true, + }); + + logger.info('Running database migrations'); + await db.migrate.latest({ + directory: path.resolve(__dirname, 'migrations'), + loadExtensions: ['.js'], + }); + + logger.info('Database initialization complete'); + return db; +} diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 31b53f6d30..d22d8c2be4 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -28,12 +28,13 @@ import { notFoundHandler, requestLoggingHandler, } from '@backstage/backend-common'; -import inventory from './plugins/inventory'; -import scaffolder from './plugins/scaffolder'; import compression from 'compression'; import cors from 'cors'; import express from 'express'; import helmet from 'helmet'; +import { buildDatabase } from './database'; +import inventory from './plugins/inventory'; +import scaffolder from './plugins/scaffolder'; import { PluginEnvironment } from './types'; const DEFAULT_PORT = 7000; @@ -43,6 +44,9 @@ const pluginEnvironment: PluginEnvironment = { }; async function main() { + const database = await buildDatabase(getRootLogger()); + console.log(await database.select().from('locations')); + const app = express(); app.use(helmet()); diff --git a/packages/backend/src/migrations/20200511113813_init.ts b/packages/backend/src/migrations/20200511113813_init.ts new file mode 100644 index 0000000000..2c13d31fcd --- /dev/null +++ b/packages/backend/src/migrations/20200511113813_init.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as Knex from 'knex'; + +export async function up(knex: Knex): Promise { + return knex.schema + .createTable('locations', table => { + table + .uuid('id') + .primary() + .comment('Auto-generated ID of the location'); + table + .string('type') + .notNullable() + .comment('The type of location'); + table + .string('target') + .notNullable() + .comment('The actual target of the location'); + }) + .createTable('components', table => { + table + .string('name') + .primary() + .comment('The name of the component'); + }); +} + +export async function down(knex: Knex): Promise { + return knex.schema.dropTable('components').dropTable('locations'); +} diff --git a/yarn.lock b/yarn.lock index 481e447150..1bdf373666 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5330,6 +5330,11 @@ array-differ@^2.0.3: resolved "https://registry.npmjs.org/array-differ/-/array-differ-2.1.0.tgz#4b9c1c3f14b906757082925769e8ab904f4801b1" integrity sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w== +array-each@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" + integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= + array-equal@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" @@ -5364,6 +5369,11 @@ array-includes@^3.0.3, array-includes@^3.1.1: es-abstract "^1.17.0" is-string "^1.0.5" +array-slice@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" + integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== + array-union@^1.0.1, array-union@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -7020,6 +7030,11 @@ color@^3.0.0, color@^3.1.2: color-convert "^1.9.1" color-string "^1.5.2" +colorette@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz#1f943e5a357fac10b4e0f5aaef3b14cdc1af6ec7" + integrity sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg== + colornames@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" @@ -7083,6 +7098,11 @@ commander@^4.0.1, commander@^4.1.1: resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + commander@~2.19.0: version "2.19.0" resolved "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" @@ -8295,6 +8315,11 @@ detect-indent@^5.0.0, detect-indent@~5.0.0: resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" @@ -9181,6 +9206,11 @@ eslint@^6.6.0, eslint@^6.8.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +esm@^3.2.25: + version "3.2.25" + resolved "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" + integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== + espree@^6.1.2: version "6.2.1" resolved "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" @@ -9834,6 +9864,22 @@ findup-sync@^3.0.0: micromatch "^3.0.4" resolve-dir "^1.0.1" +fined@^1.0.1: + version "1.2.0" + resolved "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" + integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== + dependencies: + expand-tilde "^2.0.2" + is-plain-object "^2.0.3" + object.defaults "^1.1.0" + object.pick "^1.2.0" + parse-filepath "^1.0.1" + +flagged-respawn@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" + integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== + flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" @@ -9890,6 +9936,13 @@ for-own@^0.1.3: dependencies: for-in "^1.0.1" +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= + dependencies: + for-in "^1.0.1" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -10271,6 +10324,11 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +getopts@2.2.5: + version "2.2.5" + resolved "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b" + integrity sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA== + getos@3.1.4: version "3.1.4" resolved "https://registry.npmjs.org/getos/-/getos-3.1.4.tgz#29cdf240ed10a70c049add7b6f8cb08c81876faf" @@ -11196,7 +11254,7 @@ hyphenate-style-name@^1.0.2, hyphenate-style-name@^1.0.3: resolved "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== -iconv-lite@0.4.24, iconv-lite@^0.4.21, iconv-lite@^0.4.24, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.21, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -11372,7 +11430,7 @@ inflight@^1.0.4, inflight@~1.0.6: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -11567,6 +11625,14 @@ is-absolute-url@^3.0.3: resolved "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -11952,6 +12018,13 @@ is-regexp@^1.0.0: resolved "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + is-resolvable@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" @@ -12020,6 +12093,13 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + is-utf8@^0.2.0, is-utf8@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" @@ -13329,6 +13409,27 @@ kleur@^3.0.3: resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +knex@^0.21.1: + version "0.21.1" + resolved "https://registry.npmjs.org/knex/-/knex-0.21.1.tgz#4fba7e6c58c9f459846c3090be157a732fc75e41" + integrity sha512-uWszXC2DPaLn/YznGT9wFTWUG9+kqbL4DMz+hCH789GLcLuYzq8werHPDKBJxtKvxrW/S1XIXgrTWdMypiVvsw== + dependencies: + colorette "1.1.0" + commander "^5.1.0" + debug "4.1.1" + esm "^3.2.25" + getopts "2.2.5" + inherits "~2.0.4" + interpret "^2.0.0" + liftoff "3.1.0" + lodash "^4.17.15" + mkdirp "^1.0.4" + pg-connection-string "2.2.0" + tarn "^3.0.0" + tildify "2.0.0" + uuid "^7.0.3" + v8flags "^3.1.3" + kuler@1.0.x: version "1.0.1" resolved "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz#ef7c784f36c9fb6e16dd3150d152677b2b0228a6" @@ -13579,6 +13680,20 @@ libnpx@^10.2.2: y18n "^4.0.0" yargs "^11.0.0" +liftoff@3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz#c9ba6081f908670607ee79062d700df062c52ed3" + integrity sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog== + dependencies: + extend "^3.0.0" + findup-sync "^3.0.0" + fined "^1.0.1" + flagged-respawn "^1.0.0" + is-plain-object "^2.0.4" + object.map "^1.0.0" + rechoir "^0.6.2" + resolve "^1.1.7" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -14105,6 +14220,13 @@ make-fetch-happen@^5.0.0: socks-proxy-agent "^4.0.0" ssri "^6.0.0" +make-iterator@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" + integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== + dependencies: + kind-of "^6.0.2" + makeerror@1.0.x: version "1.0.11" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -14124,7 +14246,7 @@ map-age-cleaner@^0.1.1: dependencies: p-defer "^1.0.0" -map-cache@^0.2.2: +map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= @@ -14643,6 +14765,11 @@ mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~ dependencies: minimist "^1.2.5" +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + modify-values@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -14774,6 +14901,15 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +needle@^2.2.1: + version "2.4.1" + resolved "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz#14af48732463d7475696f937626b1b993247a56a" + integrity sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + negotiator@0.6.2: version "0.6.2" resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -14935,6 +15071,22 @@ node-notifier@^6.0.0: shellwords "^0.1.1" which "^1.3.1" +node-pre-gyp@^0.11.0: + version "0.11.0" + resolved "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" + integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + node-releases@^1.1.29, node-releases@^1.1.52: version "1.1.52" resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.52.tgz#bcffee3e0a758e92e44ecfaecd0a47554b0bcba9" @@ -15081,7 +15233,7 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1: semver "^5.6.0" validate-npm-package-name "^3.0.0" -npm-packlist@^1.1.12, npm-packlist@^1.4.4, npm-packlist@^1.4.8: +npm-packlist@^1.1.12, npm-packlist@^1.1.6, npm-packlist@^1.4.4, npm-packlist@^1.4.8: version "1.4.8" resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== @@ -15261,7 +15413,7 @@ npm@^6.10.3: worker-farm "^1.7.0" write-file-atomic "^2.4.3" -npmlog@^4.1.2, npmlog@~4.1.2: +npmlog@^4.0.2, npmlog@^4.1.2, npmlog@~4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -15354,6 +15506,16 @@ object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" +object.defaults@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" + integrity sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8= + dependencies: + array-each "^1.0.1" + array-slice "^1.0.0" + for-own "^1.0.0" + isobject "^3.0.0" + object.entries@^1.1.0, object.entries@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" @@ -15382,7 +15544,15 @@ object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0 define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -object.pick@^1.3.0: +object.map@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" + integrity sha1-z4Plncj8wK1fQlDh94s7gb2AHTc= + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + +object.pick@^1.2.0, object.pick@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= @@ -15850,6 +16020,15 @@ 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: + version "1.0.2" + resolved "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" + integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= + dependencies: + is-absolute "^1.0.0" + map-cache "^0.2.0" + path-root "^0.1.1" + parse-github-repo-url@^1.3.0: version "1.4.1" resolved "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50" @@ -15988,6 +16167,18 @@ path-parse@^1.0.6: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + integrity sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0= + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + integrity sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc= + dependencies: + path-root-regex "^0.1.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -16056,6 +16247,11 @@ performance-now@^2.1.0: resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +pg-connection-string@2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.2.0.tgz#caab4d38a9de4fdc29c9317acceed752897de41c" + integrity sha512-xB/+wxcpFipUZOQcSzcgkjcNOosGhEoPSjz06jC89lv1dj7mc9bZv6wLVy8M2fVjP0a/xN0N988YDq1L0FhK3A== + picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" @@ -17342,7 +17538,7 @@ rc-progress@^2.5.2: babel-runtime "6.x" prop-types "^15.5.8" -rc@^1.0.1, rc@^1.1.6, rc@^1.2.8: +rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: version "1.2.8" resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -18390,6 +18586,13 @@ resolve@1.15.1, resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.0, r dependencies: path-parse "^1.0.6" +resolve@^1.1.7: + version "1.17.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" @@ -18476,7 +18679,7 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: +rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -18805,7 +19008,7 @@ semver-regex@^2.0.0: resolved "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -19361,6 +19564,14 @@ sprintf-js@~1.0.2: resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +sqlite3@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/sqlite3/-/sqlite3-4.2.0.tgz#49026d665e9fc4f922e56fb9711ba5b4c85c4901" + integrity sha512-roEOz41hxui2Q7uYnWsjMOTry6TcNUNmp8audCx18gF10P2NknwdpF+E+HKvz/F2NvPKGGBF4NGc+ZPQ+AABwg== + dependencies: + nan "^2.12.1" + node-pre-gyp "^0.11.0" + ssh2-streams@~0.4.10: version "0.4.10" resolved "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34" @@ -20011,7 +20222,7 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^4.4.10, tar@^4.4.12, tar@^4.4.13, tar@^4.4.8: +tar@^4, tar@^4.4.10, tar@^4.4.12, tar@^4.4.13, tar@^4.4.8: version "4.4.13" resolved "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== @@ -20036,6 +20247,11 @@ tar@^6.0.1: mkdirp "^1.0.3" yallist "^4.0.0" +tarn@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/tarn/-/tarn-3.0.0.tgz#a4082405216c0cce182b8b4cb2639c52c1e870d4" + integrity sha512-PKUnlDFODZueoA8owLehl8vLcgtA8u4dRuVbZc92tspDYZixjJL6TqYOmryf/PfP/EBX+2rgNcrj96NO+RPkdQ== + telejson@^3.2.0: version "3.3.0" resolved "https://registry.npmjs.org/telejson/-/telejson-3.3.0.tgz#6d814f3c0d254d5c4770085aad063e266b56ad03" @@ -20246,6 +20462,11 @@ thunky@^1.0.2: resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +tildify@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" + integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== + timed-out@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" @@ -20636,6 +20857,11 @@ umask@^1.1.0, umask@~1.1.0: resolved "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + undefsafe@^2.0.2: version "2.0.3" resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" @@ -20989,6 +21215,11 @@ uuid@^3.0.1, uuid@^3.3.2, uuid@^3.3.3: resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" + integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== + v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" @@ -21003,6 +21234,13 @@ v8-to-istanbul@^4.0.1: convert-source-map "^1.6.0" source-map "^0.7.3" +v8flags@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" + integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== + dependencies: + homedir-polyfill "^1.0.1" + validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3, validate-npm-package-license@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" From 2955884047b1d98d0490cc43553fb11ef0cc68dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 11 May 2020 14:20:08 +0200 Subject: [PATCH 07/73] Rename item/items -> component/components --- .../src/inventory/AggregatorInventory.ts | 16 +++++++++------- .../src/inventory/StaticInventory.ts | 14 +++++++++----- plugins/inventory-backend/src/inventory/types.ts | 4 ++-- plugins/inventory-backend/src/service/router.ts | 8 ++++---- plugins/inventory-backend/tsconfig.json | 2 +- 5 files changed, 25 insertions(+), 19 deletions(-) diff --git a/plugins/inventory-backend/src/inventory/AggregatorInventory.ts b/plugins/inventory-backend/src/inventory/AggregatorInventory.ts index b768b8eb23..59ee72067b 100644 --- a/plugins/inventory-backend/src/inventory/AggregatorInventory.ts +++ b/plugins/inventory-backend/src/inventory/AggregatorInventory.ts @@ -18,17 +18,19 @@ import { NotFoundError } from '@backstage/backend-common'; import { Component, Inventory } from './types'; export class AggregatorInventory implements Inventory { - inventories: Inventory[] = []; + #inventories: Inventory[] = []; - async list(): Promise { - const lists = await Promise.all(this.inventories.map((i) => i.list())); + async components(): Promise { + const lists = await Promise.all( + this.#inventories.map((i) => i.components()), + ); return lists.flat(); } - item(id: string): Promise { + component(id: string): Promise { return new Promise((resolve, reject) => { - const promises = this.inventories.map((i) => - i.item(id).then(resolve, () => { + const promises = this.#inventories.map((i) => + i.component(id).then(resolve, () => { // For now, just swallow errors in individual inventories; // should handle partial errors better }), @@ -40,6 +42,6 @@ export class AggregatorInventory implements Inventory { } enlist(inventory: Inventory) { - this.inventories.push(inventory); + this.#inventories.push(inventory); } } diff --git a/plugins/inventory-backend/src/inventory/StaticInventory.ts b/plugins/inventory-backend/src/inventory/StaticInventory.ts index 4569ae8ed2..6b4c29d229 100644 --- a/plugins/inventory-backend/src/inventory/StaticInventory.ts +++ b/plugins/inventory-backend/src/inventory/StaticInventory.ts @@ -18,14 +18,18 @@ import { NotFoundError } from '@backstage/backend-common'; import { Component, Inventory } from './types'; export class StaticInventory implements Inventory { - constructor(private components: Component[]) {} + #components: Component[]; - async list(): Promise { - return this.components.slice(); + constructor(components: Component[]) { + this.#components = components; } - async item(id: string): Promise { - const item = this.components.find((i) => i.id === id); + async components(): Promise { + return this.#components.slice(); + } + + async component(id: string): Promise { + const item = this.#components.find((i) => i.id === id); if (!item) { throw new NotFoundError(`Found no component with ID ${id}`); } diff --git a/plugins/inventory-backend/src/inventory/types.ts b/plugins/inventory-backend/src/inventory/types.ts index 863dccb291..4417035d85 100644 --- a/plugins/inventory-backend/src/inventory/types.ts +++ b/plugins/inventory-backend/src/inventory/types.ts @@ -19,6 +19,6 @@ export type Component = { }; export type Inventory = { - list(): Promise; - item(id: string): Promise; + components(): Promise; + component(id: string): Promise; }; diff --git a/plugins/inventory-backend/src/service/router.ts b/plugins/inventory-backend/src/service/router.ts index a05fac08be..2d093fe41e 100644 --- a/plugins/inventory-backend/src/service/router.ts +++ b/plugins/inventory-backend/src/service/router.ts @@ -32,13 +32,13 @@ export async function createRouter( const router = Router(); router - .get('/', async (req, res) => { - const components = await inventory.list(); + .get('/components', async (req, res) => { + const components = await inventory.components(); res.status(200).send(components); }) - .get('/:id', async (req, res) => { + .get('/components/:id', async (req, res) => { const { id } = req.params; - const component = await inventory.item(id); + const component = await inventory.component(id); res.status(200).send(component); }); diff --git a/plugins/inventory-backend/tsconfig.json b/plugins/inventory-backend/tsconfig.json index dd13cfe1bc..015a967f76 100644 --- a/plugins/inventory-backend/tsconfig.json +++ b/plugins/inventory-backend/tsconfig.json @@ -6,7 +6,7 @@ "sourceMap": true, "declaration": true, "strict": true, - "target": "es5", + "target": "es2019", "module": "commonjs", "esModuleInterop": true, "lib": ["es2019"], From 8b2dd21fd0dc3783f41bef81944e07aaa472f4d6 Mon Sep 17 00:00:00 2001 From: Nathan Calvank Date: Mon, 11 May 2020 07:28:20 -0700 Subject: [PATCH 08/73] Issue 660: CodeSnippet Reuseable component (#803) * Initial CodeSnippet setup in Storybook * Add support for line numbers * Pseudo-code some basic tests and leverage useTheme hook The test suite cannot currently be run, as the react-syntax-highlighter appears to be using ESM, which are not supported by Jest out-of-the-box. This should only require a simple Babel loader to remedy, but I want to make sure first that I have not misconfigured something, as configuring Babel is likely a notable scope increase for this relatively small issue. * Fix ESM Jest issue and update tests It would be ideal to have snapshot tests to cover the lightmode vs darkmode rendering. I started experimenting with directly testing the styling, and it quickly became very sloppy. * Provide more examples in stories * Add showLineNumbers to PropType validation --- packages/core/package.json | 3 +- .../CodeSnippet/CodeSnippet.stories.tsx | 81 +++++++++++++++++++ .../CodeSnippet/CodeSnippet.test.tsx | 60 ++++++++++++++ .../components/CodeSnippet/CodeSnippet.tsx | 61 ++++++++++++++ .../core/src/components/CodeSnippet/index.tsx | 17 ++++ yarn.lock | 24 ++++++ 6 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/components/CodeSnippet/CodeSnippet.stories.tsx create mode 100644 packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx create mode 100644 packages/core/src/components/CodeSnippet/CodeSnippet.tsx create mode 100644 packages/core/src/components/CodeSnippet/index.tsx diff --git a/packages/core/package.json b/packages/core/package.json index 5918fd6008..a08cf12a29 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -40,7 +40,8 @@ "react-helmet": "5.2.1", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", - "react-sparklines": "^1.7.0" + "react-sparklines": "^1.7.0", + "react-syntax-highlighter": "^12.2.1" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.4", diff --git a/packages/core/src/components/CodeSnippet/CodeSnippet.stories.tsx b/packages/core/src/components/CodeSnippet/CodeSnippet.stories.tsx new file mode 100644 index 0000000000..52db02177e --- /dev/null +++ b/packages/core/src/components/CodeSnippet/CodeSnippet.stories.tsx @@ -0,0 +1,81 @@ +/* + * 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 CodeSnippet from './CodeSnippet'; + +export default { + title: 'CodeSnippet', + component: CodeSnippet, +}; + +const containerStyle = { width: 300 }; + +const JAVASCRIPT = `const greeting = "Hello"; +const world = "World"; + +const greet = person => greeting + " " + person + "!"; + +greet(world); +`; + +const TYPESCRIPT = `const greeting: string = "Hello"; +const world: string = "World"; + +const greet = (person: string): string => greeting + " " + person + "!"; + +greet(world); +`; + +const PYTHON = `greeting = "Hello" +world = "World" + +def greet(person): + return f"{greeting} {person}!" + +greet(world) +`; + +export const Default = () => ( + +); + +export const MultipleLines = () => ( + +); + +export const LineNumbers = () => ( + +); + +export const Overflow = () => ( + <> +
+ +
+
+ +
+ +); + +export const Laguages = () => ( + <> + + + + +); diff --git a/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx b/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx new file mode 100644 index 0000000000..0d0c34a3e6 --- /dev/null +++ b/packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx @@ -0,0 +1,60 @@ +/* + * 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 { render } from '@testing-library/react'; +import { wrapInThemedTestApp } from '@backstage/test-utils'; + +import CodeSnippet from './CodeSnippet'; + +const JAVASCRIPT = `const greeting = "Hello"; +const world = "World"; + +const greet = person => gretting + " " + person + "!"; +`; + +const minProps = { + text: JAVASCRIPT, + language: 'javascript', +}; + +describe('', () => { + it('renders text without exploding', () => { + const { getByText } = render( + wrapInThemedTestApp(), + ); + expect(getByText(/"Hello"/)).toBeInTheDocument(); + expect(getByText(/"World"/)).toBeInTheDocument(); + }); + + it('renders without line numbers', () => { + const { queryByText } = render( + wrapInThemedTestApp(), + ); + expect(queryByText('1')).not.toBeInTheDocument(); + expect(queryByText('2')).not.toBeInTheDocument(); + expect(queryByText('3')).not.toBeInTheDocument(); + }); + + it('renders with line numbers', () => { + const { queryByText } = render( + wrapInThemedTestApp(), + ); + expect(queryByText(/1/)).toBeInTheDocument(); + expect(queryByText(/2/)).toBeInTheDocument(); + expect(queryByText(/3/)).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/components/CodeSnippet/CodeSnippet.tsx b/packages/core/src/components/CodeSnippet/CodeSnippet.tsx new file mode 100644 index 0000000000..c77dc5bd43 --- /dev/null +++ b/packages/core/src/components/CodeSnippet/CodeSnippet.tsx @@ -0,0 +1,61 @@ +/* + * 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 } from 'react'; +import PropTypes from 'prop-types'; +import SyntaxHighlighter from 'react-syntax-highlighter'; +import { docco, dark } from 'react-syntax-highlighter/dist/cjs/styles/hljs'; +import { useTheme } from '@material-ui/core'; +import { BackstageTheme } from '@backstage/theme'; + +type Props = { + text: string; + language: string; + showLineNumbers?: boolean; +}; + +const defaultProps = { + showLineNumbers: false, +}; + +const CodeSnippet: FC = props => { + const { text, language, showLineNumbers } = { + ...defaultProps, + ...props, + }; + + const theme = useTheme(); + const mode = theme.palette.type === 'dark' ? dark : docco; + + return ( + + {text} + + ); +}; + +// Type check for the JS files using this core component +CodeSnippet.propTypes = { + text: PropTypes.string.isRequired, + language: PropTypes.string.isRequired, + showLineNumbers: PropTypes.bool, +}; + +export default CodeSnippet; diff --git a/packages/core/src/components/CodeSnippet/index.tsx b/packages/core/src/components/CodeSnippet/index.tsx new file mode 100644 index 0000000000..71587789e5 --- /dev/null +++ b/packages/core/src/components/CodeSnippet/index.tsx @@ -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 { default } from './CodeSnippet'; diff --git a/yarn.lock b/yarn.lock index 1bdf373666..e18e7ec38f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10907,6 +10907,11 @@ highlight.js@~9.13.0: resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e" integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A== +highlight.js@~9.15.0, highlight.js@~9.15.1: + version "9.15.10" + resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2" + integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw== + history@^4.9.0: version "4.10.1" resolved "https://registry.npmjs.org/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" @@ -14141,6 +14146,14 @@ lowercase-keys@^1.0.0: resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== +lowlight@1.12.1: + version "1.12.1" + resolved "https://registry.npmjs.org/lowlight/-/lowlight-1.12.1.tgz#014acf8dd73a370e02ff1cc61debcde3bb1681eb" + integrity sha512-OqaVxMGIESnawn+TU/QMV5BJLbUghUfjDWPAtFqDYDmDtr4FnB+op8xM+pR7nKlauHNUHXGt0VgWatFB8voS5w== + dependencies: + fault "^1.0.2" + highlight.js "~9.15.0" + lowlight@~1.11.0: version "1.11.0" resolved "https://registry.npmjs.org/lowlight/-/lowlight-1.11.0.tgz#1304d83005126d4e8b1dc0f07981e9b689ec2efc" @@ -17923,6 +17936,17 @@ react-syntax-highlighter@^11.0.2: prismjs "^1.8.4" refractor "^2.4.1" +react-syntax-highlighter@^12.2.1: + version "12.2.1" + resolved "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz#14d78352da1c1c3f93c6698b70ec7c706b83493e" + integrity sha512-CTsp0ZWijwKRYFg9xhkWD4DSpQqE4vb2NKVMdPAkomnILSmsNBHE0n5GuI5zB+PU3ySVvXvdt9jo+ViD9XibCA== + dependencies: + "@babel/runtime" "^7.3.1" + highlight.js "~9.15.1" + lowlight "1.12.1" + prismjs "^1.8.4" + refractor "^2.4.1" + react-textarea-autosize@^7.1.0: version "7.1.2" resolved "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-7.1.2.tgz#70fdb333ef86bcca72717e25e623e90c336e2cda" From d6827142492e8ab5c47f7f722d11c7f76ed1aa0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 11 May 2020 16:51:54 +0200 Subject: [PATCH 09/73] Implement location management --- packages/backend/src/index.ts | 9 +-- packages/backend/src/plugins/inventory.ts | 18 ++---- packages/backend/src/types.ts | 4 +- plugins/inventory-backend/package.json | 7 +- .../src/inventory/AggregatorInventory.ts | 47 -------------- .../src/inventory/DatabaseInventory.ts | 64 +++++++++++++++++++ .../src/inventory/StaticInventory.ts | 37 +++++++++-- .../inventory-backend/src/inventory/index.ts | 2 +- .../inventory-backend/src/inventory/types.ts | 24 +++++++ .../inventory-backend/src/service/router.ts | 58 +++++++++++++++-- .../src/service/standaloneServer.ts | 10 +-- yarn.lock | 60 +++++++++++++++++ 12 files changed, 257 insertions(+), 83 deletions(-) delete mode 100644 plugins/inventory-backend/src/inventory/AggregatorInventory.ts create mode 100644 plugins/inventory-backend/src/inventory/DatabaseInventory.ts diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index d22d8c2be4..3033bc095e 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -39,13 +39,14 @@ import { PluginEnvironment } from './types'; const DEFAULT_PORT = 7000; const PORT = parseInt(process.env.PORT ?? '', 10) || DEFAULT_PORT; -const pluginEnvironment: PluginEnvironment = { - logger: getRootLogger().child({ type: 'plugin' }), -}; async function main() { const database = await buildDatabase(getRootLogger()); - console.log(await database.select().from('locations')); + + const pluginEnvironment: PluginEnvironment = { + logger: getRootLogger().child({ type: 'plugin' }), + database, + }; const app = express(); diff --git a/packages/backend/src/plugins/inventory.ts b/packages/backend/src/plugins/inventory.ts index d48a62b5fe..bf382df2ce 100644 --- a/packages/backend/src/plugins/inventory.ts +++ b/packages/backend/src/plugins/inventory.ts @@ -15,22 +15,12 @@ */ import { - AggregatorInventory, + DatabaseInventory, createRouter, - StaticInventory, } from '@backstage/plugin-inventory-backend'; -import type { PluginEnvironment } from '../types'; - -export default async function ({ logger }: PluginEnvironment) { - const inventory = new AggregatorInventory(); - inventory.enlist( - new StaticInventory([ - { id: 'component1' }, - { id: 'component2' }, - { id: 'component3' }, - { id: 'component4' }, - ]), - ); +import { PluginEnvironment } from '../types'; +export default async function({ logger, database }: PluginEnvironment) { + const inventory = new DatabaseInventory(database); return await createRouter({ inventory, logger }); } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 196337aec7..ce681a6bdd 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import type { Logger } from 'winston'; +import Knex from 'knex'; +import { Logger } from 'winston'; export type PluginEnvironment = { logger: Logger; + database: Knex; }; diff --git a/plugins/inventory-backend/package.json b/plugins/inventory-backend/package.json index 9a72daed5e..abebfe11e1 100644 --- a/plugins/inventory-backend/package.json +++ b/plugins/inventory-backend/package.json @@ -18,11 +18,16 @@ "express": "^4.17.1", "express-promise-router": "^3.0.3", "helmet": "^3.22.0", + "knex": "^0.21.1", "morgan": "^1.10.0", - "winston": "^3.2.1" + "uuid": "^8.0.0", + "winston": "^3.2.1", + "yup": "^0.28.5" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.4", + "@types/uuid": "^7.0.3", + "@types/yup": "^0.28.2", "jest-fetch-mock": "^3.0.3", "tsc-watch": "^4.2.3" }, diff --git a/plugins/inventory-backend/src/inventory/AggregatorInventory.ts b/plugins/inventory-backend/src/inventory/AggregatorInventory.ts deleted file mode 100644 index 59ee72067b..0000000000 --- a/plugins/inventory-backend/src/inventory/AggregatorInventory.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 { NotFoundError } from '@backstage/backend-common'; -import { Component, Inventory } from './types'; - -export class AggregatorInventory implements Inventory { - #inventories: Inventory[] = []; - - async components(): Promise { - const lists = await Promise.all( - this.#inventories.map((i) => i.components()), - ); - return lists.flat(); - } - - component(id: string): Promise { - return new Promise((resolve, reject) => { - const promises = this.#inventories.map((i) => - i.component(id).then(resolve, () => { - // For now, just swallow errors in individual inventories; - // should handle partial errors better - }), - ); - Promise.all(promises).then(() => - reject(new NotFoundError(`Found no component with ID ${id}`)), - ); - }); - } - - enlist(inventory: Inventory) { - this.#inventories.push(inventory); - } -} diff --git a/plugins/inventory-backend/src/inventory/DatabaseInventory.ts b/plugins/inventory-backend/src/inventory/DatabaseInventory.ts new file mode 100644 index 0000000000..0afa1a6bcf --- /dev/null +++ b/plugins/inventory-backend/src/inventory/DatabaseInventory.ts @@ -0,0 +1,64 @@ +/* + * 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 { NotFoundError } from '@backstage/backend-common'; +import Knex from 'knex'; +import { v4 as uuidv4 } from 'uuid'; +import { AddLocationRequest, Component, Inventory, Location } from './types'; + +export class DatabaseInventory implements Inventory { + constructor(private readonly database: Knex) {} + + async components(): Promise { + throw new Error('Not supported'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async component(id: string): Promise { + throw new Error('Not supported'); + } + + async addLocation(location: AddLocationRequest): Promise { + const id = uuidv4(); + const { type, target } = location; + await this.database('locations').insert({ id, type, target }); + return await this.location(id); + } + + async removeLocation(id: string): Promise { + const result = await this.database('locations') + .where({ id }) + .del(); + + if (!result) { + throw new NotFoundError(`Found no location with ID ${id}`); + } + } + + async location(id: string): Promise { + const items = await this.database('locations') + .where({ id }) + .select(); + if (!items.length) { + throw new NotFoundError(`Found no location with ID ${id}`); + } + return items[0]; + } + + async locations(): Promise { + return await this.database('locations').select(); + } +} diff --git a/plugins/inventory-backend/src/inventory/StaticInventory.ts b/plugins/inventory-backend/src/inventory/StaticInventory.ts index 6b4c29d229..3ce6dfcd76 100644 --- a/plugins/inventory-backend/src/inventory/StaticInventory.ts +++ b/plugins/inventory-backend/src/inventory/StaticInventory.ts @@ -15,24 +15,49 @@ */ import { NotFoundError } from '@backstage/backend-common'; -import { Component, Inventory } from './types'; +import { v4 as uuidv4 } from 'uuid'; +import { AddLocationRequest, Component, Inventory, Location } from './types'; export class StaticInventory implements Inventory { - #components: Component[]; + private _components: Component[]; + private _locations: Location[]; - constructor(components: Component[]) { - this.#components = components; + constructor(components: Component[], locations: Location[]) { + this._components = components; + this._locations = locations; } async components(): Promise { - return this.#components.slice(); + return this._components.slice(); } async component(id: string): Promise { - const item = this.#components.find((i) => i.id === id); + const item = this._components.find(i => i.id === id); if (!item) { throw new NotFoundError(`Found no component with ID ${id}`); } return item; } + + async addLocation(location: AddLocationRequest): Promise { + const l = { id: uuidv4(), type: location.type, target: location.target }; + this._locations.push(l); + return l; + } + + async removeLocation(id: string): Promise { + this._locations = this._locations.filter(l => l.id !== id); + } + + async location(id: string): Promise { + const location = this._locations.find(l => l.id === id); + if (!location) { + throw new NotFoundError(`Found no location with ID ${id}`); + } + return location; + } + + async locations(): Promise { + return this._locations; + } } diff --git a/plugins/inventory-backend/src/inventory/index.ts b/plugins/inventory-backend/src/inventory/index.ts index 3affa1109a..e61ff20e19 100644 --- a/plugins/inventory-backend/src/inventory/index.ts +++ b/plugins/inventory-backend/src/inventory/index.ts @@ -14,6 +14,6 @@ * limitations under the License. */ -export * from './AggregatorInventory'; +export * from './DatabaseInventory'; export * from './StaticInventory'; export * from './types'; diff --git a/plugins/inventory-backend/src/inventory/types.ts b/plugins/inventory-backend/src/inventory/types.ts index 4417035d85..6d6ef38df1 100644 --- a/plugins/inventory-backend/src/inventory/types.ts +++ b/plugins/inventory-backend/src/inventory/types.ts @@ -14,11 +14,35 @@ * limitations under the License. */ +import * as yup from 'yup'; + export type Component = { id: string; }; +export type Location = { + id: string; + type: string; + target: string; +}; + +export type AddLocationRequest = { + type: string; + target: string; +}; + +export const addLocationRequestShape: yup.Schema = yup + .object({ + type: yup.string().required(), + target: yup.string().required(), + }) + .noUnknown(); + export type Inventory = { components(): Promise; component(id: string): Promise; + addLocation(location: AddLocationRequest): Promise; + removeLocation(id: string): Promise; + location(id: string): Promise; + locations(): Promise; }; diff --git a/plugins/inventory-backend/src/service/router.ts b/plugins/inventory-backend/src/service/router.ts index 2d093fe41e..06077d29c4 100644 --- a/plugins/inventory-backend/src/service/router.ts +++ b/plugins/inventory-backend/src/service/router.ts @@ -14,23 +14,51 @@ * limitations under the License. */ -import express from 'express'; -import { Logger } from 'winston'; +import { InputError } from '@backstage/backend-common'; +import express, { Request } from 'express'; import Router from 'express-promise-router'; -import { Inventory } from '../inventory'; +import { Logger } from 'winston'; +import yup from 'yup'; +import { addLocationRequestShape, Inventory } from '../inventory'; export interface RouterOptions { inventory: Inventory; logger: Logger; } +async function validateRequestBody( + req: Request, + schema: yup.Schema, +): Promise { + const contentType = req.header('content-type'); + if (!contentType) { + throw new InputError('Content-Type missing'); + } else if (!contentType.match(/^application\/json($|;)/)) { + throw new InputError('Illegal Content-Type'); + } + + const body = req.body; + if (!body) { + throw new InputError('Missing request body'); + } + + try { + await schema.validate(body, { strict: true }); + } catch (e) { + throw new InputError(`Malformed request: ${e}`); + } + + return body as T; +} + export async function createRouter( options: RouterOptions, ): Promise { const inventory = options.inventory; const logger = options.logger.child({ plugin: 'inventory' }); - const router = Router(); + + // Components router .get('/components', async (req, res) => { const components = await inventory.components(); @@ -42,6 +70,28 @@ export async function createRouter( res.status(200).send(component); }); + // Locations + router + .post('/locations', async (req, res) => { + const input = await validateRequestBody(req, addLocationRequestShape); + const output = await inventory.addLocation(input); + res.status(201).send(output); + }) + .get('/locations', async (req, res) => { + const output = await inventory.locations(); + res.status(200).send(output); + }) + .get('/locations/:id', async (req, res) => { + const { id } = req.params; + const output = await inventory.location(id); + res.status(200).send(output); + }) + .delete('/locations/:id', async (req, res) => { + const { id } = req.params; + await inventory.removeLocation(id); + res.status(200).send(); + }); + const app = express(); app.set('logger', logger); app.use('/', router); diff --git a/plugins/inventory-backend/src/service/standaloneServer.ts b/plugins/inventory-backend/src/service/standaloneServer.ts index be339c5ecc..76d57ff852 100644 --- a/plugins/inventory-backend/src/service/standaloneServer.ts +++ b/plugins/inventory-backend/src/service/standaloneServer.ts @@ -16,7 +16,7 @@ import { Server } from 'http'; import { Logger } from 'winston'; -import { AggregatorInventory, StaticInventory } from '../inventory'; +import { StaticInventory } from '../inventory'; import { createStandaloneApplication } from './standaloneApplication'; export interface ServerOptions { @@ -30,14 +30,14 @@ export async function startStandaloneServer( ): Promise { const logger = options.logger.child({ service: 'inventory-backend' }); - const inventory = new AggregatorInventory(); - inventory.enlist( - new StaticInventory([ + const inventory = new StaticInventory( + [ { id: 'component1' }, { id: 'component2' }, { id: 'component3' }, { id: 'component4' }, - ]), + ], + [], ); logger.debug('Creating application...'); diff --git a/yarn.lock b/yarn.lock index 1bdf373666..89382c3db0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -956,6 +956,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.9.6": + version "7.9.6" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" + integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.4.0", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -4542,6 +4549,11 @@ dependencies: source-map "^0.6.1" +"@types/uuid@^7.0.3": + version "7.0.3" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.3.tgz#45cd03e98e758f8581c79c535afbd4fc27ba7ac8" + integrity sha512-PUdqTZVrNYTNcIhLHkiaYzoOIaUi5LFg/XLerAdgvwQrUCx+oSbtoBze1AMyvYbcwzUSNC+Isl58SM4Sm/6COw== + "@types/webpack-dev-server@*", "@types/webpack-dev-server@^3.10.0": version "3.10.1" resolved "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.10.1.tgz#93b7133cc9dab1ca1b76659f5ef8b763ad54c28a" @@ -4598,6 +4610,11 @@ dependencies: "@types/yargs-parser" "*" +"@types/yup@^0.28.2": + version "0.28.2" + resolved "https://registry.npmjs.org/@types/yup/-/yup-0.28.2.tgz#ea6597d7bd48ecea94b81f8de7fd60fba07a4628" + integrity sha512-VvXPMsvUnkDIWHFIalJROljLSv9m9TaXKng5sLFUkmuybgY64AeBbZ5tOPZ9sjSpqhJFeOXIc22JSQDO6SCONg== + "@types/zen-observable@^0.8.0": version "0.8.0" resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" @@ -9907,6 +9924,11 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +fn-name@~3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c" + integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA== + focus-lock@^0.6.6: version "0.6.6" resolved "https://registry.npmjs.org/focus-lock/-/focus-lock-0.6.6.tgz#98119a755a38cfdbeda0280eaa77e307eee850c7" @@ -13910,6 +13932,11 @@ lockfile@^1.0.4: dependencies: signal-exit "^3.0.2" +lodash-es@^4.17.11: + version "4.17.15" + resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" + integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== + lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -17287,6 +17314,11 @@ prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, object-assign "^4.1.1" react-is "^16.8.1" +property-expr@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/property-expr/-/property-expr-2.0.2.tgz#fff2a43919135553a3bc2fdd94bdb841965b2330" + integrity sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g== + property-information@^5.0.0: version "5.4.0" resolved "https://registry.npmjs.org/property-information/-/property-information-5.4.0.tgz#16e08f13f4e5c4a7be2e4ec431c01c4f8dba869a" @@ -20186,6 +20218,11 @@ symbol.prototype.description@^1.0.0: es-abstract "^1.17.0-next.1" has-symbols "^1.0.1" +synchronous-promise@^2.0.10: + version "2.0.11" + resolved "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.11.tgz#e92022b0754e916f556d3ace1626d24a24214b7e" + integrity sha512-8/L5FOCjnlK0FoAfj+NqdCaImMKvEyOEzGmdfcezKp5K9HIukm4akx72endvM87eS/gU8kOxiMQflpC/CdkQAg== + table@^5.2.3: version "5.4.6" resolved "https://registry.npmjs.org/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" @@ -20580,6 +20617,11 @@ toposort@^1.0.0: resolved "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= + touch@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -21220,6 +21262,11 @@ uuid@^7.0.3: resolved "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== +uuid@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" + integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== + v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" @@ -22199,6 +22246,19 @@ yn@3.1.1: resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== +yup@^0.28.5: + version "0.28.5" + resolved "https://registry.npmjs.org/yup/-/yup-0.28.5.tgz#85cabb4000d3623ef69be81551190692e631a8a5" + integrity sha512-7JZcvpUGUxMKoaEtcoMEM8lCWRaueGNH/A3EhL/UWqfbFm3uloiI+x59Yq4nzhbbYWUTwAsCteaZOJ+VbqI1uw== + dependencies: + "@babel/runtime" "^7.9.6" + fn-name "~3.0.0" + lodash "^4.17.15" + lodash-es "^4.17.11" + property-expr "^2.0.2" + synchronous-promise "^2.0.10" + toposort "^2.0.2" + zen-observable@^0.8.15: version "0.8.15" resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" From 52c48ce1a7e3daac1c08647e2166a0021cfd64fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 11 May 2020 22:06:38 +0200 Subject: [PATCH 10/73] Move inventory migrations into the plugin itself --- packages/backend/.gitignore | 1 - packages/backend/src/database.ts | 39 ------------------- packages/backend/src/index.ts | 22 ++++++----- packages/backend/src/plugins/inventory.ts | 2 +- .../src/inventory/DatabaseInventory.ts | 10 +++++ .../src/migrations/20200511113813_init.ts | 0 6 files changed, 24 insertions(+), 50 deletions(-) delete mode 100644 packages/backend/.gitignore delete mode 100644 packages/backend/src/database.ts rename {packages/backend => plugins/inventory-backend}/src/migrations/20200511113813_init.ts (100%) diff --git a/packages/backend/.gitignore b/packages/backend/.gitignore deleted file mode 100644 index 1097a1ff3c..0000000000 --- a/packages/backend/.gitignore +++ /dev/null @@ -1 +0,0 @@ -dev.sqlite3 diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts deleted file mode 100644 index afd46b87e7..0000000000 --- a/packages/backend/src/database.ts +++ /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. - */ - -import knex from 'knex'; -import path from 'path'; -import { Logger } from 'winston'; - -export async function buildDatabase(logger: Logger) { - logger.info('Connecting to database'); - const db = knex({ - client: 'sqlite3', - connection: { - filename: './dev.sqlite3', - }, - useNullAsDefault: true, - }); - - logger.info('Running database migrations'); - await db.migrate.latest({ - directory: path.resolve(__dirname, 'migrations'), - loadExtensions: ['.js'], - }); - - logger.info('Database initialization complete'); - return db; -} diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 3033bc095e..b917c6c559 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -32,7 +32,7 @@ import compression from 'compression'; import cors from 'cors'; import express from 'express'; import helmet from 'helmet'; -import { buildDatabase } from './database'; +import knex from 'knex'; import inventory from './plugins/inventory'; import scaffolder from './plugins/scaffolder'; import { PluginEnvironment } from './types'; @@ -40,14 +40,18 @@ import { PluginEnvironment } from './types'; const DEFAULT_PORT = 7000; const PORT = parseInt(process.env.PORT ?? '', 10) || DEFAULT_PORT; -async function main() { - const database = await buildDatabase(getRootLogger()); - - const pluginEnvironment: PluginEnvironment = { - logger: getRootLogger().child({ type: 'plugin' }), - database, +function createEnv(plugin: string): PluginEnvironment { + return { + logger: getRootLogger().child({ type: 'plugin', plugin }), + database: knex({ + client: 'sqlite3', + connection: ':memory:', + useNullAsDefault: true, + }), }; +} +async function main() { const app = express(); app.use(helmet()); @@ -55,8 +59,8 @@ async function main() { app.use(compression()); app.use(express.json()); app.use(requestLoggingHandler()); - app.use('/inventory', await inventory(pluginEnvironment)); - app.use('/scaffolder', await scaffolder(pluginEnvironment)); + app.use('/inventory', await inventory(createEnv('inventory'))); + app.use('/scaffolder', await scaffolder(createEnv('scaffolder'))); app.use(notFoundHandler()); app.use(errorHandler()); diff --git a/packages/backend/src/plugins/inventory.ts b/packages/backend/src/plugins/inventory.ts index bf382df2ce..96d3fc52e7 100644 --- a/packages/backend/src/plugins/inventory.ts +++ b/packages/backend/src/plugins/inventory.ts @@ -21,6 +21,6 @@ import { import { PluginEnvironment } from '../types'; export default async function({ logger, database }: PluginEnvironment) { - const inventory = new DatabaseInventory(database); + const inventory = await DatabaseInventory.create(database); return await createRouter({ inventory, logger }); } diff --git a/plugins/inventory-backend/src/inventory/DatabaseInventory.ts b/plugins/inventory-backend/src/inventory/DatabaseInventory.ts index 0afa1a6bcf..58e2666552 100644 --- a/plugins/inventory-backend/src/inventory/DatabaseInventory.ts +++ b/plugins/inventory-backend/src/inventory/DatabaseInventory.ts @@ -15,11 +15,21 @@ */ import { NotFoundError } from '@backstage/backend-common'; +import path from 'path'; import Knex from 'knex'; import { v4 as uuidv4 } from 'uuid'; import { AddLocationRequest, Component, Inventory, Location } from './types'; export class DatabaseInventory implements Inventory { + static async create(database: Knex): Promise { + await database.migrate.latest({ + directory: path.resolve(__dirname, '..', 'migrations'), + loadExtensions: ['.js'], + }); + + return new DatabaseInventory(database); + } + constructor(private readonly database: Knex) {} async components(): Promise { diff --git a/packages/backend/src/migrations/20200511113813_init.ts b/plugins/inventory-backend/src/migrations/20200511113813_init.ts similarity index 100% rename from packages/backend/src/migrations/20200511113813_init.ts rename to plugins/inventory-backend/src/migrations/20200511113813_init.ts From e8bbf91647f7682d78c5260c1a611dd5e1061d00 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 12 May 2020 06:12:13 +0000 Subject: [PATCH 11/73] build(deps): bump tar from 6.0.1 to 6.0.2 Bumps [tar](https://github.com/npm/node-tar) from 6.0.1 to 6.0.2. - [Release notes](https://github.com/npm/node-tar/releases) - [Changelog](https://github.com/npm/node-tar/blob/master/CHANGELOG.md) - [Commits](https://github.com/npm/node-tar/compare/v6.0.1...v6.0.2) Signed-off-by: dependabot-preview[bot] --- yarn.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index e18e7ec38f..f0247719ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6711,11 +6711,16 @@ chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.3.1: optionalDependencies: fsevents "~2.1.2" -chownr@^1.1.1, chownr@^1.1.2, chownr@^1.1.3, chownr@^1.1.4: +chownr@^1.1.1, chownr@^1.1.2, chownr@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" @@ -14766,10 +14771,10 @@ mkdirp-promise@^5.0.1: dependencies: mkdirp "*" -mkdirp@*, mkdirp@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" - integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== +mkdirp@*, 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== mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.5" @@ -14778,11 +14783,6 @@ mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~ dependencies: minimist "^1.2.5" -mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - modify-values@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" @@ -20260,11 +20260,11 @@ tar@^4, tar@^4.4.10, tar@^4.4.12, tar@^4.4.13, tar@^4.4.8: yallist "^3.0.3" tar@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/tar/-/tar-6.0.1.tgz#7b3bd6c313cb6e0153770108f8d70ac298607efa" - integrity sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q== + version "6.0.2" + resolved "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39" + integrity sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg== dependencies: - chownr "^1.1.3" + chownr "^2.0.0" fs-minipass "^2.0.0" minipass "^3.0.0" minizlib "^2.1.0" From f74ebf31ca9e438786e5d82d0dc236fe64500476 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 12 May 2020 06:16:08 +0000 Subject: [PATCH 12/73] build(deps-dev): bump @types/webpack from 4.41.8 to 4.41.12 Bumps [@types/webpack](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/webpack) from 4.41.8 to 4.41.12. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/webpack) Signed-off-by: dependabot-preview[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e18e7ec38f..dafcc124a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4568,9 +4568,9 @@ source-map "^0.6.1" "@types/webpack@*", "@types/webpack@^4.41.7": - version "4.41.8" - resolved "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.8.tgz#d2244f5f612ee30230a5c8c4ae678bce90d27277" - integrity sha512-mh4litLHTlDG84TGCFv1pZldndI34vkrW9Mks++Zx4KET7DRMoCXUvLbTISiuF4++fMgNnhV9cc1nCXJQyBYbQ== + version "4.41.12" + resolved "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.12.tgz#0386ee2a2814368e2f2397abb036c0bf173ff6c3" + integrity sha512-BpCtM4NnBen6W+KEhrL9jKuZCXVtiH6+0b6cxdvNt2EwU949Al334PjQSl2BeAyvAX9mgoNNG21wvjP3xZJJ5w== dependencies: "@types/anymatch" "*" "@types/node" "*" From 978fcee4a9920630a72ab24e2a9a072cfc70e806 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 12 May 2020 06:17:02 +0000 Subject: [PATCH 13/73] build(deps-dev): bump husky from 4.2.3 to 4.2.5 Bumps [husky](https://github.com/typicode/husky) from 4.2.3 to 4.2.5. - [Release notes](https://github.com/typicode/husky/releases) - [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md) - [Commits](https://github.com/typicode/husky/compare/v4.2.3...v4.2.5) Signed-off-by: dependabot-preview[bot] --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index e18e7ec38f..2cc9c142ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7147,7 +7147,7 @@ compare-func@^1.3.1: array-ify "^1.0.0" dot-prop "^3.0.0" -compare-versions@^3.5.1: +compare-versions@^3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== @@ -11239,13 +11239,13 @@ humanize-ms@^1.2.1: ms "^2.0.0" husky@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/husky/-/husky-4.2.3.tgz#3b18d2ee5febe99e27f2983500202daffbc3151e" - integrity sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ== + version "4.2.5" + resolved "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36" + integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ== dependencies: - chalk "^3.0.0" + chalk "^4.0.0" ci-info "^2.0.0" - compare-versions "^3.5.1" + compare-versions "^3.6.0" cosmiconfig "^6.0.0" find-versions "^3.2.0" opencollective-postinstall "^2.0.2" From e121fc6cad493bae586d4a35786f9259bebcab6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 12 May 2020 11:16:09 +0200 Subject: [PATCH 14/73] Rename inventory to catalog --- packages/app/package.json | 2 +- packages/app/src/components/Root/Root.tsx | 2 +- packages/app/src/plugins.ts | 2 +- packages/backend/package.json | 2 +- packages/backend/src/index.ts | 4 ++-- .../src/plugins/{inventory.ts => catalog.ts} | 10 +++++----- .../.eslintrc.js | 0 .../README.md | 8 ++++---- .../package.json | 2 +- .../src/catalog/DatabaseCatalog.ts} | 16 ++++++--------- .../src/catalog/StaticCatalog.ts} | 10 +++++----- .../src/catalog}/index.ts | 4 ++-- .../src/catalog}/types.ts | 2 +- .../src/index.test.ts | 0 .../src/index.ts | 2 +- .../src/migrations/20200511113813_init.ts | 19 +++++------------- .../src/run.ts | 0 .../src/service/router.ts | 20 +++++++++---------- .../src/service/standaloneApplication.ts | 8 ++++---- .../src/service/standaloneServer.ts | 8 ++++---- .../src/setupTests.ts | 0 .../tsconfig.json | 0 plugins/{inventory => catalog}/.eslintrc.js | 0 plugins/catalog/README.md | 13 ++++++++++++ plugins/{inventory => catalog}/dev/index.tsx | 0 plugins/{inventory => catalog}/package.json | 2 +- .../CatalogPage/CatalogPage.test.tsx} | 6 +++--- .../components/CatalogPage/CatalogPage.tsx} | 6 +++--- .../src/components/CatalogPage}/index.ts | 2 +- plugins/{inventory => catalog}/src/index.ts | 0 .../{inventory => catalog}/src/plugin.test.ts | 2 +- plugins/{inventory => catalog}/src/plugin.ts | 6 +++--- .../{inventory => catalog}/src/setupTests.ts | 0 plugins/{inventory => catalog}/tsconfig.json | 0 plugins/inventory/README.md | 13 ------------ plugins/scaffolder/README.md | 9 +++------ plugins/scaffolder/src/plugin.test.ts | 2 +- 37 files changed, 83 insertions(+), 99 deletions(-) rename packages/backend/src/plugins/{inventory.ts => catalog.ts} (72%) rename plugins/{inventory-backend => catalog-backend}/.eslintrc.js (100%) rename plugins/{inventory-backend => catalog-backend}/README.md (58%) rename plugins/{inventory-backend => catalog-backend}/package.json (95%) rename plugins/{inventory-backend/src/inventory/DatabaseInventory.ts => catalog-backend/src/catalog/DatabaseCatalog.ts} (81%) rename plugins/{inventory-backend/src/inventory/StaticInventory.ts => catalog-backend/src/catalog/StaticCatalog.ts} (84%) rename plugins/{inventory-backend/src/inventory => catalog-backend/src/catalog}/index.ts (89%) rename plugins/{inventory-backend/src/inventory => catalog-backend/src/catalog}/types.ts (97%) rename plugins/{inventory-backend => catalog-backend}/src/index.test.ts (100%) rename plugins/{inventory-backend => catalog-backend}/src/index.ts (95%) rename plugins/{inventory-backend => catalog-backend}/src/migrations/20200511113813_init.ts (71%) rename plugins/{inventory-backend => catalog-backend}/src/run.ts (100%) rename plugins/{inventory-backend => catalog-backend}/src/service/router.ts (82%) rename plugins/{inventory-backend => catalog-backend}/src/service/standaloneApplication.ts (88%) rename plugins/{inventory-backend => catalog-backend}/src/service/standaloneServer.ts (89%) rename plugins/{inventory-backend => catalog-backend}/src/setupTests.ts (100%) rename plugins/{inventory-backend => catalog-backend}/tsconfig.json (100%) rename plugins/{inventory => catalog}/.eslintrc.js (100%) create mode 100644 plugins/catalog/README.md rename plugins/{inventory => catalog}/dev/index.tsx (100%) rename plugins/{inventory => catalog}/package.json (96%) rename plugins/{inventory/src/components/InventoryPage/InventoryPage.test.tsx => catalog/src/components/CatalogPage/CatalogPage.test.tsx} (91%) rename plugins/{inventory/src/components/InventoryPage/InventoryPage.tsx => catalog/src/components/CatalogPage/CatalogPage.tsx} (94%) rename plugins/{inventory/src/components/InventoryPage => catalog/src/components/CatalogPage}/index.ts (93%) rename plugins/{inventory => catalog}/src/index.ts (100%) rename plugins/{inventory => catalog}/src/plugin.test.ts (95%) rename plugins/{inventory => catalog}/src/plugin.ts (84%) rename plugins/{inventory => catalog}/src/setupTests.ts (100%) rename plugins/{inventory => catalog}/tsconfig.json (100%) delete mode 100644 plugins/inventory/README.md diff --git a/packages/app/package.json b/packages/app/package.json index 40659d4561..baf0e9628c 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -7,7 +7,7 @@ "@backstage/core": "^0.1.1-alpha.4", "@backstage/plugin-explore": "^0.1.1-alpha.4", "@backstage/plugin-home-page": "^0.1.1-alpha.4", - "@backstage/plugin-inventory": "^0.1.1-alpha.4", + "@backstage/plugin-catalog": "^0.1.1-alpha.4", "@backstage/plugin-lighthouse": "^0.1.1-alpha.4", "@backstage/plugin-scaffolder": "^0.1.1-alpha.4", "@backstage/plugin-tech-radar": "^0.1.1-alpha.4", diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 6854ce015b..55cba439bd 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -85,7 +85,7 @@ const Root: FC<{}> = ({ children }) => ( - + diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index 3669f83dba..c8d972bf8d 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -16,7 +16,7 @@ export { plugin as HomePagePlugin } from '@backstage/plugin-home-page'; export { plugin as WelcomePlugin } from '@backstage/plugin-welcome'; export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse'; -export { plugin as InventoryPlugin } from '@backstage/plugin-inventory'; +export { plugin as CatalogPlugin } from '@backstage/plugin-catalog'; export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder'; export { plugin as TechRadar } from '@backstage/plugin-tech-radar'; export { plugin as Explore } from '@backstage/plugin-explore'; diff --git a/packages/backend/package.json b/packages/backend/package.json index 36eb09fe8a..10ec8eb6bb 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -17,7 +17,7 @@ }, "dependencies": { "@backstage/backend-common": "0.1.1-alpha.4", - "@backstage/plugin-inventory-backend": "0.1.1-alpha.4", + "@backstage/plugin-catalog-backend": "0.1.1-alpha.4", "@backstage/plugin-scaffolder-backend": "0.1.1-alpha.4", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index b917c6c559..8c685d3fb7 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -33,7 +33,7 @@ import cors from 'cors'; import express from 'express'; import helmet from 'helmet'; import knex from 'knex'; -import inventory from './plugins/inventory'; +import catalog from './plugins/catalog'; import scaffolder from './plugins/scaffolder'; import { PluginEnvironment } from './types'; @@ -59,7 +59,7 @@ async function main() { app.use(compression()); app.use(express.json()); app.use(requestLoggingHandler()); - app.use('/inventory', await inventory(createEnv('inventory'))); + app.use('/catalog', await catalog(createEnv('catalog'))); app.use('/scaffolder', await scaffolder(createEnv('scaffolder'))); app.use(notFoundHandler()); app.use(errorHandler()); diff --git a/packages/backend/src/plugins/inventory.ts b/packages/backend/src/plugins/catalog.ts similarity index 72% rename from packages/backend/src/plugins/inventory.ts rename to packages/backend/src/plugins/catalog.ts index 96d3fc52e7..2c6459e071 100644 --- a/packages/backend/src/plugins/inventory.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -15,12 +15,12 @@ */ import { - DatabaseInventory, + DatabaseCatalog, createRouter, -} from '@backstage/plugin-inventory-backend'; +} from '@backstage/plugin-catalog-backend'; import { PluginEnvironment } from '../types'; -export default async function({ logger, database }: PluginEnvironment) { - const inventory = await DatabaseInventory.create(database); - return await createRouter({ inventory, logger }); +export default async function ({ logger, database }: PluginEnvironment) { + const catalog = await DatabaseCatalog.create(database); + return await createRouter({ catalog, logger }); } diff --git a/plugins/inventory-backend/.eslintrc.js b/plugins/catalog-backend/.eslintrc.js similarity index 100% rename from plugins/inventory-backend/.eslintrc.js rename to plugins/catalog-backend/.eslintrc.js diff --git a/plugins/inventory-backend/README.md b/plugins/catalog-backend/README.md similarity index 58% rename from plugins/inventory-backend/README.md rename to plugins/catalog-backend/README.md index bc224ee42e..051bc50417 100644 --- a/plugins/inventory-backend/README.md +++ b/plugins/catalog-backend/README.md @@ -1,13 +1,13 @@ -# Inventory Backend +# Catalog Backend WORK IN PROGRESS -This is the backend part of the default inventory plugin. +This is the backend part of the default catalog plugin. It responds to requests from the frontend part, and fulfills them by delegating -to your existing inventory related services. +to your existing catalog related services. ## Links -- (Frontend part of the plugin)[https://github.com/spotify/backstage/tree/master/plugins/inventory] +- (Frontend part of the plugin)[https://github.com/spotify/backstage/tree/master/plugins/catalog] - (The Backstage homepage)[https://backstage.io] diff --git a/plugins/inventory-backend/package.json b/plugins/catalog-backend/package.json similarity index 95% rename from plugins/inventory-backend/package.json rename to plugins/catalog-backend/package.json index abebfe11e1..88b8048f7b 100644 --- a/plugins/inventory-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,5 +1,5 @@ { - "name": "@backstage/plugin-inventory-backend", + "name": "@backstage/plugin-catalog-backend", "version": "0.1.1-alpha.4", "main": "dist", "license": "Apache-2.0", diff --git a/plugins/inventory-backend/src/inventory/DatabaseInventory.ts b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts similarity index 81% rename from plugins/inventory-backend/src/inventory/DatabaseInventory.ts rename to plugins/catalog-backend/src/catalog/DatabaseCatalog.ts index 58e2666552..4ef99108da 100644 --- a/plugins/inventory-backend/src/inventory/DatabaseInventory.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts @@ -18,16 +18,16 @@ import { NotFoundError } from '@backstage/backend-common'; import path from 'path'; import Knex from 'knex'; import { v4 as uuidv4 } from 'uuid'; -import { AddLocationRequest, Component, Inventory, Location } from './types'; +import { AddLocationRequest, Component, Catalog, Location } from './types'; -export class DatabaseInventory implements Inventory { - static async create(database: Knex): Promise { +export class DatabaseCatalog implements Catalog { + static async create(database: Knex): Promise { await database.migrate.latest({ directory: path.resolve(__dirname, '..', 'migrations'), loadExtensions: ['.js'], }); - return new DatabaseInventory(database); + return new DatabaseCatalog(database); } constructor(private readonly database: Knex) {} @@ -49,9 +49,7 @@ export class DatabaseInventory implements Inventory { } async removeLocation(id: string): Promise { - const result = await this.database('locations') - .where({ id }) - .del(); + const result = await this.database('locations').where({ id }).del(); if (!result) { throw new NotFoundError(`Found no location with ID ${id}`); @@ -59,9 +57,7 @@ export class DatabaseInventory implements Inventory { } async location(id: string): Promise { - const items = await this.database('locations') - .where({ id }) - .select(); + const items = await this.database('locations').where({ id }).select(); if (!items.length) { throw new NotFoundError(`Found no location with ID ${id}`); } diff --git a/plugins/inventory-backend/src/inventory/StaticInventory.ts b/plugins/catalog-backend/src/catalog/StaticCatalog.ts similarity index 84% rename from plugins/inventory-backend/src/inventory/StaticInventory.ts rename to plugins/catalog-backend/src/catalog/StaticCatalog.ts index 3ce6dfcd76..2003982681 100644 --- a/plugins/inventory-backend/src/inventory/StaticInventory.ts +++ b/plugins/catalog-backend/src/catalog/StaticCatalog.ts @@ -16,9 +16,9 @@ import { NotFoundError } from '@backstage/backend-common'; import { v4 as uuidv4 } from 'uuid'; -import { AddLocationRequest, Component, Inventory, Location } from './types'; +import { AddLocationRequest, Component, Catalog, Location } from './types'; -export class StaticInventory implements Inventory { +export class StaticCatalog implements Catalog { private _components: Component[]; private _locations: Location[]; @@ -32,7 +32,7 @@ export class StaticInventory implements Inventory { } async component(id: string): Promise { - const item = this._components.find(i => i.id === id); + const item = this._components.find((i) => i.id === id); if (!item) { throw new NotFoundError(`Found no component with ID ${id}`); } @@ -46,11 +46,11 @@ export class StaticInventory implements Inventory { } async removeLocation(id: string): Promise { - this._locations = this._locations.filter(l => l.id !== id); + this._locations = this._locations.filter((l) => l.id !== id); } async location(id: string): Promise { - const location = this._locations.find(l => l.id === id); + const location = this._locations.find((l) => l.id === id); if (!location) { throw new NotFoundError(`Found no location with ID ${id}`); } diff --git a/plugins/inventory-backend/src/inventory/index.ts b/plugins/catalog-backend/src/catalog/index.ts similarity index 89% rename from plugins/inventory-backend/src/inventory/index.ts rename to plugins/catalog-backend/src/catalog/index.ts index e61ff20e19..b4bc07c6bb 100644 --- a/plugins/inventory-backend/src/inventory/index.ts +++ b/plugins/catalog-backend/src/catalog/index.ts @@ -14,6 +14,6 @@ * limitations under the License. */ -export * from './DatabaseInventory'; -export * from './StaticInventory'; +export * from './DatabaseCatalog'; +export * from './StaticCatalog'; export * from './types'; diff --git a/plugins/inventory-backend/src/inventory/types.ts b/plugins/catalog-backend/src/catalog/types.ts similarity index 97% rename from plugins/inventory-backend/src/inventory/types.ts rename to plugins/catalog-backend/src/catalog/types.ts index 6d6ef38df1..f8b00626f0 100644 --- a/plugins/inventory-backend/src/inventory/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -38,7 +38,7 @@ export const addLocationRequestShape: yup.Schema = yup }) .noUnknown(); -export type Inventory = { +export type Catalog = { components(): Promise; component(id: string): Promise; addLocation(location: AddLocationRequest): Promise; diff --git a/plugins/inventory-backend/src/index.test.ts b/plugins/catalog-backend/src/index.test.ts similarity index 100% rename from plugins/inventory-backend/src/index.test.ts rename to plugins/catalog-backend/src/index.test.ts diff --git a/plugins/inventory-backend/src/index.ts b/plugins/catalog-backend/src/index.ts similarity index 95% rename from plugins/inventory-backend/src/index.ts rename to plugins/catalog-backend/src/index.ts index 52b4ed9a53..16f59d2c17 100644 --- a/plugins/inventory-backend/src/index.ts +++ b/plugins/catalog-backend/src/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export * from './inventory'; +export * from './catalog'; export * from './service/router'; diff --git a/plugins/inventory-backend/src/migrations/20200511113813_init.ts b/plugins/catalog-backend/src/migrations/20200511113813_init.ts similarity index 71% rename from plugins/inventory-backend/src/migrations/20200511113813_init.ts rename to plugins/catalog-backend/src/migrations/20200511113813_init.ts index 2c13d31fcd..0db92a1f45 100644 --- a/plugins/inventory-backend/src/migrations/20200511113813_init.ts +++ b/plugins/catalog-backend/src/migrations/20200511113813_init.ts @@ -18,25 +18,16 @@ import * as Knex from 'knex'; export async function up(knex: Knex): Promise { return knex.schema - .createTable('locations', table => { - table - .uuid('id') - .primary() - .comment('Auto-generated ID of the location'); - table - .string('type') - .notNullable() - .comment('The type of location'); + .createTable('locations', (table) => { + table.uuid('id').primary().comment('Auto-generated ID of the location'); + table.string('type').notNullable().comment('The type of location'); table .string('target') .notNullable() .comment('The actual target of the location'); }) - .createTable('components', table => { - table - .string('name') - .primary() - .comment('The name of the component'); + .createTable('components', (table) => { + table.string('name').primary().comment('The name of the component'); }); } diff --git a/plugins/inventory-backend/src/run.ts b/plugins/catalog-backend/src/run.ts similarity index 100% rename from plugins/inventory-backend/src/run.ts rename to plugins/catalog-backend/src/run.ts diff --git a/plugins/inventory-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts similarity index 82% rename from plugins/inventory-backend/src/service/router.ts rename to plugins/catalog-backend/src/service/router.ts index 06077d29c4..3bdbedb766 100644 --- a/plugins/inventory-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -19,10 +19,10 @@ import express, { Request } from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import yup from 'yup'; -import { addLocationRequestShape, Inventory } from '../inventory'; +import { addLocationRequestShape, Catalog } from '../catalog'; export interface RouterOptions { - inventory: Inventory; + catalog: Catalog; logger: Logger; } @@ -54,19 +54,19 @@ async function validateRequestBody( export async function createRouter( options: RouterOptions, ): Promise { - const inventory = options.inventory; - const logger = options.logger.child({ plugin: 'inventory' }); + const catalog = options.catalog; + const logger = options.logger.child({ plugin: 'catalog' }); const router = Router(); // Components router .get('/components', async (req, res) => { - const components = await inventory.components(); + const components = await catalog.components(); res.status(200).send(components); }) .get('/components/:id', async (req, res) => { const { id } = req.params; - const component = await inventory.component(id); + const component = await catalog.component(id); res.status(200).send(component); }); @@ -74,21 +74,21 @@ export async function createRouter( router .post('/locations', async (req, res) => { const input = await validateRequestBody(req, addLocationRequestShape); - const output = await inventory.addLocation(input); + const output = await catalog.addLocation(input); res.status(201).send(output); }) .get('/locations', async (req, res) => { - const output = await inventory.locations(); + const output = await catalog.locations(); res.status(200).send(output); }) .get('/locations/:id', async (req, res) => { const { id } = req.params; - const output = await inventory.location(id); + const output = await catalog.location(id); res.status(200).send(output); }) .delete('/locations/:id', async (req, res) => { const { id } = req.params; - await inventory.removeLocation(id); + await catalog.removeLocation(id); res.status(200).send(); }); diff --git a/plugins/inventory-backend/src/service/standaloneApplication.ts b/plugins/catalog-backend/src/service/standaloneApplication.ts similarity index 88% rename from plugins/inventory-backend/src/service/standaloneApplication.ts rename to plugins/catalog-backend/src/service/standaloneApplication.ts index 8f0518bb8d..da39be8410 100644 --- a/plugins/inventory-backend/src/service/standaloneApplication.ts +++ b/plugins/catalog-backend/src/service/standaloneApplication.ts @@ -24,19 +24,19 @@ import cors from 'cors'; import express from 'express'; import helmet from 'helmet'; import { Logger } from 'winston'; -import { Inventory } from '../inventory'; +import { Catalog } from '../catalog'; import { createRouter } from './router'; export interface ApplicationOptions { enableCors: boolean; - inventory: Inventory; + catalog: Catalog; logger: Logger; } export async function createStandaloneApplication( options: ApplicationOptions, ): Promise { - const { enableCors, inventory, logger } = options; + const { enableCors, catalog, logger } = options; const app = express(); app.use(helmet()); @@ -46,7 +46,7 @@ export async function createStandaloneApplication( app.use(compression()); app.use(express.json()); app.use(requestLoggingHandler()); - app.use('/', await createRouter({ inventory, logger })); + app.use('/', await createRouter({ catalog, logger })); app.use(notFoundHandler()); app.use(errorHandler()); diff --git a/plugins/inventory-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts similarity index 89% rename from plugins/inventory-backend/src/service/standaloneServer.ts rename to plugins/catalog-backend/src/service/standaloneServer.ts index 76d57ff852..4607f8ca25 100644 --- a/plugins/inventory-backend/src/service/standaloneServer.ts +++ b/plugins/catalog-backend/src/service/standaloneServer.ts @@ -16,7 +16,7 @@ import { Server } from 'http'; import { Logger } from 'winston'; -import { StaticInventory } from '../inventory'; +import { StaticCatalog } from '../catalog'; import { createStandaloneApplication } from './standaloneApplication'; export interface ServerOptions { @@ -28,9 +28,9 @@ export interface ServerOptions { export async function startStandaloneServer( options: ServerOptions, ): Promise { - const logger = options.logger.child({ service: 'inventory-backend' }); + const logger = options.logger.child({ service: 'catalog-backend' }); - const inventory = new StaticInventory( + const catalog = new StaticCatalog( [ { id: 'component1' }, { id: 'component2' }, @@ -43,7 +43,7 @@ export async function startStandaloneServer( logger.debug('Creating application...'); const app = await createStandaloneApplication({ enableCors: options.enableCors, - inventory, + catalog, logger, }); diff --git a/plugins/inventory-backend/src/setupTests.ts b/plugins/catalog-backend/src/setupTests.ts similarity index 100% rename from plugins/inventory-backend/src/setupTests.ts rename to plugins/catalog-backend/src/setupTests.ts diff --git a/plugins/inventory-backend/tsconfig.json b/plugins/catalog-backend/tsconfig.json similarity index 100% rename from plugins/inventory-backend/tsconfig.json rename to plugins/catalog-backend/tsconfig.json diff --git a/plugins/inventory/.eslintrc.js b/plugins/catalog/.eslintrc.js similarity index 100% rename from plugins/inventory/.eslintrc.js rename to plugins/catalog/.eslintrc.js diff --git a/plugins/catalog/README.md b/plugins/catalog/README.md new file mode 100644 index 0000000000..52bbd0d0c3 --- /dev/null +++ b/plugins/catalog/README.md @@ -0,0 +1,13 @@ +# Catalog Frontend + +WORK IN PROGRESS + +This is the frontend part of the default catalog plugin. + +It will implement the core API for handling your catalog of software, and +supply the base views to show and manage them. + +## Links + +- (Backend part of the plugin)[https://github.com/spotify/backstage/tree/master/plugins/catalog-backend] +- (The Backstage homepage)[https://backstage.io] diff --git a/plugins/inventory/dev/index.tsx b/plugins/catalog/dev/index.tsx similarity index 100% rename from plugins/inventory/dev/index.tsx rename to plugins/catalog/dev/index.tsx diff --git a/plugins/inventory/package.json b/plugins/catalog/package.json similarity index 96% rename from plugins/inventory/package.json rename to plugins/catalog/package.json index c5221fba4c..2e786d9a3c 100644 --- a/plugins/inventory/package.json +++ b/plugins/catalog/package.json @@ -1,5 +1,5 @@ { - "name": "@backstage/plugin-inventory", + "name": "@backstage/plugin-catalog", "version": "0.1.1-alpha.4", "main": "dist/index.esm.js", "types": "dist/index.d.ts", diff --git a/plugins/inventory/src/components/InventoryPage/InventoryPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx similarity index 91% rename from plugins/inventory/src/components/InventoryPage/InventoryPage.test.tsx rename to plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index fe17da308c..02b7fcacff 100644 --- a/plugins/inventory/src/components/InventoryPage/InventoryPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -17,16 +17,16 @@ import React from 'react'; import { render } from '@testing-library/react'; import mockFetch from 'jest-fetch-mock'; -import InventoryPage from './InventoryPage'; +import CatalogPage from './CatalogPage'; import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; -describe('InventoryPage', () => { +describe('CatalogPage', () => { it('should render', async () => { mockFetch.mockResponse(() => new Promise(() => {})); const rendered = render( - + , ); expect(await rendered.findByText('backstage-backend')).toBeInTheDocument(); diff --git a/plugins/inventory/src/components/InventoryPage/InventoryPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx similarity index 94% rename from plugins/inventory/src/components/InventoryPage/InventoryPage.tsx rename to plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 647899eb35..5d530f8416 100644 --- a/plugins/inventory/src/components/InventoryPage/InventoryPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -31,10 +31,10 @@ const STATIC_DATA = [ { id: 'backstage-microsite', kind: 'website' }, ]; -const InventoryPage: FC<{}> = () => { +const CatalogPage: FC<{}> = () => { return ( -
+
All of it @@ -62,4 +62,4 @@ const InventoryPage: FC<{}> = () => { ); }; -export default InventoryPage; +export default CatalogPage; diff --git a/plugins/inventory/src/components/InventoryPage/index.ts b/plugins/catalog/src/components/CatalogPage/index.ts similarity index 93% rename from plugins/inventory/src/components/InventoryPage/index.ts rename to plugins/catalog/src/components/CatalogPage/index.ts index 7a11d39f10..61182e316f 100644 --- a/plugins/inventory/src/components/InventoryPage/index.ts +++ b/plugins/catalog/src/components/CatalogPage/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { default } from './InventoryPage'; +export { default } from './CatalogPage'; diff --git a/plugins/inventory/src/index.ts b/plugins/catalog/src/index.ts similarity index 100% rename from plugins/inventory/src/index.ts rename to plugins/catalog/src/index.ts diff --git a/plugins/inventory/src/plugin.test.ts b/plugins/catalog/src/plugin.test.ts similarity index 95% rename from plugins/inventory/src/plugin.test.ts rename to plugins/catalog/src/plugin.test.ts index 4fac72593f..427efc39e1 100644 --- a/plugins/inventory/src/plugin.test.ts +++ b/plugins/catalog/src/plugin.test.ts @@ -16,7 +16,7 @@ import { plugin } from './plugin'; -describe('inventory', () => { +describe('catalog', () => { it('should export plugin', () => { expect(plugin).toBeDefined(); }); diff --git a/plugins/inventory/src/plugin.ts b/plugins/catalog/src/plugin.ts similarity index 84% rename from plugins/inventory/src/plugin.ts rename to plugins/catalog/src/plugin.ts index 197dd3cd38..ff3a53ce5b 100644 --- a/plugins/inventory/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -15,11 +15,11 @@ */ import { createPlugin } from '@backstage/core'; -import InventoryPage from './components/InventoryPage'; +import CatalogPage from './components/CatalogPage'; export const plugin = createPlugin({ - id: 'inventory', + id: 'catalog', register({ router }) { - router.registerRoute('/inventory', InventoryPage); + router.registerRoute('/catalog', CatalogPage); }, }); diff --git a/plugins/inventory/src/setupTests.ts b/plugins/catalog/src/setupTests.ts similarity index 100% rename from plugins/inventory/src/setupTests.ts rename to plugins/catalog/src/setupTests.ts diff --git a/plugins/inventory/tsconfig.json b/plugins/catalog/tsconfig.json similarity index 100% rename from plugins/inventory/tsconfig.json rename to plugins/catalog/tsconfig.json diff --git a/plugins/inventory/README.md b/plugins/inventory/README.md deleted file mode 100644 index e77a77148f..0000000000 --- a/plugins/inventory/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Inventory Frontend - -WORK IN PROGRESS - -This is the frontend part of the default inventory plugin. - -It will implement the core API for handling your inventory of software, and -supply the base views to show and manage them. - -## Links - -- (Backend part of the plugin)[https://github.com/spotify/backstage/tree/master/plugins/inventory-backend] -- (The Backstage homepage)[https://backstage.io] diff --git a/plugins/scaffolder/README.md b/plugins/scaffolder/README.md index e77a77148f..5893ee86ba 100644 --- a/plugins/scaffolder/README.md +++ b/plugins/scaffolder/README.md @@ -1,13 +1,10 @@ -# Inventory Frontend +# Scaffolder Frontend WORK IN PROGRESS -This is the frontend part of the default inventory plugin. - -It will implement the core API for handling your inventory of software, and -supply the base views to show and manage them. +This is the frontend part of the default scaffolder plugin. ## Links -- (Backend part of the plugin)[https://github.com/spotify/backstage/tree/master/plugins/inventory-backend] +- (Backend part of the plugin)[https://github.com/spotify/backstage/tree/master/plugins/scaffolder-backend] - (The Backstage homepage)[https://backstage.io] diff --git a/plugins/scaffolder/src/plugin.test.ts b/plugins/scaffolder/src/plugin.test.ts index 4fac72593f..3b4c92168d 100644 --- a/plugins/scaffolder/src/plugin.test.ts +++ b/plugins/scaffolder/src/plugin.test.ts @@ -16,7 +16,7 @@ import { plugin } from './plugin'; -describe('inventory', () => { +describe('scaffolder', () => { it('should export plugin', () => { expect(plugin).toBeDefined(); }); From 017b5b8c1d9ad307ae2952821fcbf29ce814b7ab Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Tue, 12 May 2020 13:17:00 +0200 Subject: [PATCH 15/73] Added Zalando's copyright info to our NOTICE (#822) --- NOTICE | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NOTICE b/NOTICE index 306a8b20f1..0967e9bd8d 100644 --- a/NOTICE +++ b/NOTICE @@ -1,2 +1,5 @@ Backstage Copyright 2020 Spotify AB + +Portions of this software were developed by third-party software vendors: +- Tech Radar Plugin (https://opensource.zalando.com/tech-radar/), Copyright (c) 2017 Zalando SE From e399a3f8d0e38973e98b7182c584839dbd13a81f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Adaszy=C5=84ski?= Date: Tue, 12 May 2020 15:10:59 +0200 Subject: [PATCH 16/73] Update status component: new color and shape of indicator (#811) Co-authored-by: Wojciech Adaszynski --- .../src/components/Status/Status.stories.tsx | 31 +++----- .../core/src/components/Status/Status.tsx | 78 +++++++++---------- packages/core/src/components/Status/index.ts | 3 +- packages/theme/src/themes.ts | 24 +++--- packages/theme/src/types.ts | 1 + 5 files changed, 60 insertions(+), 77 deletions(-) diff --git a/packages/core/src/components/Status/Status.stories.tsx b/packages/core/src/components/Status/Status.stories.tsx index 7a6a58caa8..0e3717d082 100644 --- a/packages/core/src/components/Status/Status.stories.tsx +++ b/packages/core/src/components/Status/Status.stories.tsx @@ -17,8 +17,7 @@ import React from 'react'; import { StatusError, - StatusFailed, - StatusNA, + StatusAborted, StatusOK, StatusPending, StatusRunning, @@ -34,45 +33,34 @@ export default { const data = [ { - status: , + status: OK, label: 'OK', usage: 'Deployment successful', }, { - status: , - label: 'Warning', + status: Warning, usage: 'CPU utilization at 90%', }, { - status: , - label: 'Error', + status: Error, usage: 'Service could not be created', }, { - status: , - label: 'Failed', - usage: 'Build for PR #34 failed', + status: Aborted, + usage: 'Build for PR #34 aborted', }, { - status: , - label: 'Pending', + status: Pending, usage: 'Job is waiting', }, { - status: , - label: 'Running', + status: Running, usage: 'Job is running', }, - { - status: , - label: 'N/A', - usage: 'Not sure what to do', - }, ]; const columns = [ { field: 'status', title: 'Status' }, - { field: 'label', title: 'Label' }, { field: 'usage', title: 'Example usage' }, ]; @@ -97,7 +85,6 @@ export const Default = () => ( export const statusOK = () => ; export const statusWarning = () => ; export const statusError = () => ; -export const statusFailed = () => ; +export const statusAborted = () => ; export const statusPending = () => ; export const statusRunning = () => ; -export const statusNA = () => ; diff --git a/packages/core/src/components/Status/Status.tsx b/packages/core/src/components/Status/Status.tsx index a232aceb62..b34f8a3c31 100644 --- a/packages/core/src/components/Status/Status.tsx +++ b/packages/core/src/components/Status/Status.tsx @@ -21,32 +21,46 @@ import React, { FC } from 'react'; const useStyles = makeStyles((theme) => ({ status: { - width: 12, - height: 12, - display: 'inline-block', - marginRight: 1, + fontWeight: 500, + '&::before': { + width: '0.7em', + height: '0.7em', + display: 'inline-block', + marginRight: 8, + borderRadius: '50%', + content: '""', + }, }, ok: { - backgroundColor: theme.palette.status.ok, - borderRadius: '50%', + '&::before': { + backgroundColor: theme.palette.status.ok, + }, }, warning: { - backgroundColor: theme.palette.status.warning, + '&::before': { + backgroundColor: theme.palette.status.warning, + }, }, error: { - // Use same for Failed status. - width: '0', - height: '0', - borderLeft: '7px solid transparent', - borderRight: '7px solid transparent', - borderBottom: `14px solid ${theme.palette.status.error}`, + '&::before': { + backgroundColor: theme.palette.status.error, + }, }, pending: { - backgroundColor: theme.palette.status.pending, + '&::before': { + backgroundColor: theme.palette.status.pending, + }, }, running: { - animation: '$blink 0.8s step-start 0s infinite', - backgroundColor: theme.palette.status.running, + '&::before': { + backgroundColor: theme.palette.status.running, + animation: '$blink 0.8s step-start 0s infinite', + }, + }, + aborted: { + '&::before': { + backgroundColor: theme.palette.status.aborted, + }, }, '@keyframes blink': { '50%': { @@ -57,43 +71,23 @@ const useStyles = makeStyles((theme) => ({ export const StatusOK: FC<{}> = (props) => { const classes = useStyles(props); - return ( - - ); + return ; }; export const StatusWarning: FC<{}> = (props) => { const classes = useStyles(props); return ( - + ); }; export const StatusError: FC<{}> = (props) => { const classes = useStyles(props); return ( - + ); }; -export const StatusNA: FC<{}> = (props) => ( - - N/A - -); - export const StatusPending: FC<{}> = (props) => { const classes = useStyles(props); return ( @@ -116,12 +110,12 @@ export const StatusRunning: FC<{}> = (props) => { ); }; -export const StatusFailed: FC<{}> = (props) => { +export const StatusAborted: FC<{}> = (props) => { const classes = useStyles(props); return ( ); diff --git a/packages/core/src/components/Status/index.ts b/packages/core/src/components/Status/index.ts index 35d2ca1c48..4c0fd6322b 100644 --- a/packages/core/src/components/Status/index.ts +++ b/packages/core/src/components/Status/index.ts @@ -16,8 +16,7 @@ export { StatusError, - StatusFailed, - StatusNA, + StatusAborted, StatusOK, StatusPending, StatusRunning, diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts index f2c26e6e4a..86fb5a1982 100644 --- a/packages/theme/src/themes.ts +++ b/packages/theme/src/themes.ts @@ -23,11 +23,12 @@ export const lightTheme = createTheme({ default: '#F8F8F8', }, status: { - ok: '#1db855', - warning: '#f49b20', - error: '#CA001B', - running: '#BEBEBE', - pending: '#5BC0DE', + ok: '#1DB954', + warning: '#FF9800', + error: '#E22134', + running: '#2E77D0', + pending: '#FFED51', + aborted: '#757575', background: '#F8F8F8', }, bursts: { @@ -63,12 +64,13 @@ export const darkTheme = createTheme({ default: '#333333', }, status: { - ok: '#1db855', - warning: '#f49b20', - error: '#CA001B', - running: '#BEBEBE', - pending: '#5BC0DE', - background: '#282828', + ok: '#1DB954', + warning: '#FF9800', + error: '#E22134', + running: '#2E77D0', + pending: '#FFED51', + aborted: '#757575', + background: '#F8F8F8', }, bursts: { fontColor: '#FEFEFE', diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index c020fbb4e1..54bca9d61b 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -27,6 +27,7 @@ type PaletteAdditions = { error: string; pending: string; running: string; + aborted: string; background: string; }; border: string; From 92ec964b98ab4deb509d3dc52415a7d05caa6b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 12 May 2020 16:17:41 +0200 Subject: [PATCH 17/73] Implement first-light location refresh loop --- packages/backend/src/plugins/catalog.ts | 4 +- .../catalog-backend/fixtures/component1.yaml | 1 + plugins/catalog-backend/package.json | 2 + .../src/catalog/DatabaseCatalog.test.ts | 52 +++++++++++ .../src/catalog/DatabaseCatalog.ts | 93 +++++++++++++++++-- .../src/catalog/StaticCatalog.ts | 10 +- plugins/catalog-backend/src/catalog/types.ts | 8 +- .../src/service/standaloneServer.ts | 8 +- yarn.lock | 7 ++ 9 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 plugins/catalog-backend/fixtures/component1.yaml create mode 100644 plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index 2c6459e071..fb2814d640 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -20,7 +20,7 @@ import { } from '@backstage/plugin-catalog-backend'; import { PluginEnvironment } from '../types'; -export default async function ({ logger, database }: PluginEnvironment) { - const catalog = await DatabaseCatalog.create(database); +export default async function({ logger, database }: PluginEnvironment) { + const catalog = await DatabaseCatalog.create(database, logger); return await createRouter({ catalog, logger }); } diff --git a/plugins/catalog-backend/fixtures/component1.yaml b/plugins/catalog-backend/fixtures/component1.yaml new file mode 100644 index 0000000000..8db5cfc851 --- /dev/null +++ b/plugins/catalog-backend/fixtures/component1.yaml @@ -0,0 +1 @@ +name: Component1 diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 88b8048f7b..1a819c28b3 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -17,11 +17,13 @@ "cors": "^2.8.5", "express": "^4.17.1", "express-promise-router": "^3.0.3", + "fs-extra": "^9.0.0", "helmet": "^3.22.0", "knex": "^0.21.1", "morgan": "^1.10.0", "uuid": "^8.0.0", "winston": "^3.2.1", + "yaml": "^1.9.2", "yup": "^0.28.5" }, "devDependencies": { diff --git a/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts b/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts new file mode 100644 index 0000000000..038314e653 --- /dev/null +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts @@ -0,0 +1,52 @@ +/* + * 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 knex from 'knex'; +import path from 'path'; +import { PassThrough } from 'stream'; +import winston from 'winston'; +import { DatabaseCatalog } from './DatabaseCatalog'; + +describe('DatabaseCatalog', () => { + const database = knex({ + client: 'sqlite3', + connection: ':memory:', + useNullAsDefault: true, + }); + + const logger = winston.createLogger({ + transports: [new winston.transports.Stream({ stream: new PassThrough() })], + }); + + beforeEach(async () => { + await database.migrate.latest({ + directory: path.resolve(__dirname, '..', 'migrations'), + loadExtensions: ['.ts'], + }); + }); + + it('instantiates', () => { + const catalog = new DatabaseCatalog(database, logger); + expect(catalog).toBeDefined(); + }); + + describe(`refreshLocations`, () => { + it('works with no locations added', async () => { + const catalog = new DatabaseCatalog(database, logger); + await expect(catalog.refreshLocations()).resolves.toBeUndefined(); + }); + }); +}); diff --git a/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts index 4ef99108da..d28278c496 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts @@ -15,29 +15,79 @@ */ import { NotFoundError } from '@backstage/backend-common'; -import path from 'path'; import Knex from 'knex'; +import path from 'path'; +import fs from 'fs-extra'; import { v4 as uuidv4 } from 'uuid'; -import { AddLocationRequest, Component, Catalog, Location } from './types'; +import { Logger } from 'winston'; +import { + AddLocationRequest, + Catalog, + Component, + Location, + componentShape, +} from './types'; +import YAML from 'yaml'; + +async function readLocation(location: Location): Promise { + switch (location.type) { + case 'file': { + let parsed; + try { + const raw = await fs.readFile(location.target, 'utf8'); + parsed = YAML.parse(raw); + } catch (e) { + throw new Error(`Unable to read "${location.target}", ${e}`); + } + + try { + return [await componentShape.validate(parsed, { strict: true })]; + } catch (e) { + throw new Error( + `Malformed file contents at "${location.target}", ${e}`, + ); + } + } + + default: + throw new Error(`Unknown type "${location.type}"`); + } +} export class DatabaseCatalog implements Catalog { - static async create(database: Knex): Promise { + static async create( + database: Knex, + logger: Logger, + ): Promise { await database.migrate.latest({ directory: path.resolve(__dirname, '..', 'migrations'), loadExtensions: ['.js'], }); - return new DatabaseCatalog(database); + const databaseCatalog = new DatabaseCatalog(database, logger); + + const startRefresh = async () => { + for (;;) { + await databaseCatalog.refreshLocations(); + await new Promise(r => setTimeout(r, 10000)); + } + }; + startRefresh(); + + return databaseCatalog; } - constructor(private readonly database: Knex) {} + constructor( + private readonly database: Knex, + private readonly logger: Logger, + ) {} async components(): Promise { throw new Error('Not supported'); } // eslint-disable-next-line @typescript-eslint/no-unused-vars - async component(id: string): Promise { + async component(name: string): Promise { throw new Error('Not supported'); } @@ -49,15 +99,42 @@ export class DatabaseCatalog implements Catalog { } async removeLocation(id: string): Promise { - const result = await this.database('locations').where({ id }).del(); + const result = await this.database('locations') + .where({ id }) + .del(); if (!result) { throw new NotFoundError(`Found no location with ID ${id}`); } } + async refreshLocations(): Promise { + const locations = await this.locations(); + for (const location of locations) { + try { + this.logger.debug(`Attempting refresh of location: ${location.id}`); + const components = await readLocation(location); + for (const component of components) { + await this.database.transaction(async tx => { + await tx('components') + .insert(component) + .catch(() => + tx('components') + .where({ name: component.name }) + .update(component), + ); + }); + } + } catch (e) { + this.logger.debug(`Failed to update location "${location.id}", ${e}`); + } + } + } + async location(id: string): Promise { - const items = await this.database('locations').where({ id }).select(); + const items = await this.database('locations') + .where({ id }) + .select(); if (!items.length) { throw new NotFoundError(`Found no location with ID ${id}`); } diff --git a/plugins/catalog-backend/src/catalog/StaticCatalog.ts b/plugins/catalog-backend/src/catalog/StaticCatalog.ts index 2003982681..43229929b8 100644 --- a/plugins/catalog-backend/src/catalog/StaticCatalog.ts +++ b/plugins/catalog-backend/src/catalog/StaticCatalog.ts @@ -31,10 +31,10 @@ export class StaticCatalog implements Catalog { return this._components.slice(); } - async component(id: string): Promise { - const item = this._components.find((i) => i.id === id); + async component(name: string): Promise { + const item = this._components.find(i => i.name === name); if (!item) { - throw new NotFoundError(`Found no component with ID ${id}`); + throw new NotFoundError(`Found no component with name ${name}`); } return item; } @@ -46,11 +46,11 @@ export class StaticCatalog implements Catalog { } async removeLocation(id: string): Promise { - this._locations = this._locations.filter((l) => l.id !== id); + this._locations = this._locations.filter(l => l.id !== id); } async location(id: string): Promise { - const location = this._locations.find((l) => l.id === id); + const location = this._locations.find(l => l.id === id); if (!location) { throw new NotFoundError(`Found no location with ID ${id}`); } diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index f8b00626f0..abac94345d 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -17,9 +17,15 @@ import * as yup from 'yup'; export type Component = { - id: string; + name: string; }; +export const componentShape: yup.Schema = yup + .object({ + name: yup.string().required(), + }) + .unknown(); + export type Location = { id: string; type: string; diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts index 4607f8ca25..d5c46d7293 100644 --- a/plugins/catalog-backend/src/service/standaloneServer.ts +++ b/plugins/catalog-backend/src/service/standaloneServer.ts @@ -32,10 +32,10 @@ export async function startStandaloneServer( const catalog = new StaticCatalog( [ - { id: 'component1' }, - { id: 'component2' }, - { id: 'component3' }, - { id: 'component4' }, + { name: 'component1' }, + { name: 'component2' }, + { name: 'component3' }, + { name: 'component4' }, ], [], ); diff --git a/yarn.lock b/yarn.lock index 6b716af0dd..e9a138e465 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22091,6 +22091,13 @@ yaml@^1.7.2: dependencies: "@babel/runtime" "^7.8.7" +yaml@^1.9.2: + version "1.9.2" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz#f0cfa865f003ab707663e4f04b3956957ea564ed" + integrity sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg== + dependencies: + "@babel/runtime" "^7.9.2" + yargs-parser@^10.0.0: version "10.1.0" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" From 3435ae9a134ed2184a162ddd143babc8c7c09013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Adaszy=C5=84ski?= Date: Wed, 13 May 2020 08:11:06 +0200 Subject: [PATCH 18/73] Add Register Component plugin (#826) Co-authored-by: Wojciech Adaszynski --- packages/app/package.json | 1 + packages/app/src/plugins.ts | 1 + plugins/register-component/.eslintrc.js | 3 + plugins/register-component/README.md | 5 + plugins/register-component/dev/index.tsx | 22 ++++ plugins/register-component/package.json | 41 +++++++ .../RegisterComponentForm.test.tsx | 60 ++++++++++ .../RegisterComponentForm.tsx | 110 ++++++++++++++++++ .../components/RegisterComponentForm/index.ts | 17 +++ .../RegisterComponentPage.test.tsx | 34 ++++++ .../RegisterComponentPage.tsx | 65 +++++++++++ .../components/RegisterComponentPage/index.ts | 17 +++ plugins/register-component/src/index.ts | 17 +++ plugins/register-component/src/plugin.test.ts | 23 ++++ plugins/register-component/src/plugin.ts | 25 ++++ plugins/register-component/src/setupTests.ts | 18 +++ .../src/util/validate.test.ts | 60 ++++++++++ .../register-component/src/util/validate.ts | 27 +++++ plugins/register-component/tsconfig.json | 5 + yarn.lock | 5 + 20 files changed, 556 insertions(+) create mode 100644 plugins/register-component/.eslintrc.js create mode 100644 plugins/register-component/README.md create mode 100644 plugins/register-component/dev/index.tsx create mode 100644 plugins/register-component/package.json create mode 100644 plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx create mode 100644 plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx create mode 100644 plugins/register-component/src/components/RegisterComponentForm/index.ts create mode 100644 plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx create mode 100644 plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx create mode 100644 plugins/register-component/src/components/RegisterComponentPage/index.ts create mode 100644 plugins/register-component/src/index.ts create mode 100644 plugins/register-component/src/plugin.test.ts create mode 100644 plugins/register-component/src/plugin.ts create mode 100644 plugins/register-component/src/setupTests.ts create mode 100644 plugins/register-component/src/util/validate.test.ts create mode 100644 plugins/register-component/src/util/validate.ts create mode 100644 plugins/register-component/tsconfig.json diff --git a/packages/app/package.json b/packages/app/package.json index baf0e9628c..255f66e5e2 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -9,6 +9,7 @@ "@backstage/plugin-home-page": "^0.1.1-alpha.4", "@backstage/plugin-catalog": "^0.1.1-alpha.4", "@backstage/plugin-lighthouse": "^0.1.1-alpha.4", + "@backstage/plugin-register-component": "^0.1.1-alpha.4", "@backstage/plugin-scaffolder": "^0.1.1-alpha.4", "@backstage/plugin-tech-radar": "^0.1.1-alpha.4", "@backstage/plugin-welcome": "^0.1.1-alpha.4", diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index c8d972bf8d..1930acae92 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -20,3 +20,4 @@ export { plugin as CatalogPlugin } from '@backstage/plugin-catalog'; export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder'; export { plugin as TechRadar } from '@backstage/plugin-tech-radar'; export { plugin as Explore } from '@backstage/plugin-explore'; +export { plugin as RegisterComponent } from '@backstage/plugin-register-component'; diff --git a/plugins/register-component/.eslintrc.js b/plugins/register-component/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/register-component/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/register-component/README.md b/plugins/register-component/README.md new file mode 100644 index 0000000000..729a38955c --- /dev/null +++ b/plugins/register-component/README.md @@ -0,0 +1,5 @@ +# [WIP] register-component + +Welcome to the register-component plugin! + +This plugin allows you to submit your Backstage component using your software's YAML config. diff --git a/plugins/register-component/dev/index.tsx b/plugins/register-component/dev/index.tsx new file mode 100644 index 0000000000..d97643057b --- /dev/null +++ b/plugins/register-component/dev/index.tsx @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp() + .registerPlugin(plugin) + .render(); diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json new file mode 100644 index 0000000000..9113006ad7 --- /dev/null +++ b/plugins/register-component/package.json @@ -0,0 +1,41 @@ +{ + "name": "@backstage/plugin-register-component", + "version": "0.1.1-alpha.4", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts", + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/core": "^0.1.1-alpha.4", + "@backstage/theme": "^0.1.1-alpha.4", + "@material-ui/core": "^4.9.1", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-hook-form": "^5.7.2", + "react-use": "^13.24.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/dev-utils": "^0.1.1-alpha.4", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@testing-library/user-event": "^7.1.2", + "@types/jest": "^24.0.0", + "@types/node": "^12.0.0", + "@types/testing-library__jest-dom": "^5.0.4", + "jest-fetch-mock": "^3.0.3" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx new file mode 100644 index 0000000000..9203093d48 --- /dev/null +++ b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx @@ -0,0 +1,60 @@ +/* + * 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 { render, fireEvent } from '@testing-library/react'; +import RegisterComponentForm from './RegisterComponentForm'; + +describe('RegisterComponentForm', () => { + it('should initially render a disabled button', async () => { + const rendered = render( + , + ); + expect( + await rendered.findByText( + 'Enter the full path to the service-info.yaml file in GHE to start tracking your component. It must be in a public repo, on the master branch.', + ), + ).toBeInTheDocument(); + + const submit = (await rendered.getByRole('button')) as HTMLButtonElement; + expect(submit.disabled).toBeTruthy(); + }); + + it('should enable a submit form when data when component url is set ', async () => { + const rendered = render( + , + ); + const input = (await rendered.getByRole('textbox')) as HTMLInputElement; + fireEvent.change(input, { + target: { value: 'https://example.com/blob/master/service.yaml' }, + }); + const submit = (await rendered.findByText('Submit')) as HTMLButtonElement; + + expect(submit.disabled).toBeFalsy(); + }); + + it('should hide input on submission ', async () => { + const rendered = render( + , + ); + + expect( + await rendered.findByText( + 'Your component is being registered. Please wait.', + ), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx new file mode 100644 index 0000000000..d279afac25 --- /dev/null +++ b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx @@ -0,0 +1,110 @@ +/* + * 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 } from 'react'; +import { + Button, + FormControl, + FormHelperText, + TextField, + Typography, +} from '@material-ui/core'; +import { useForm } from 'react-hook-form'; +import { makeStyles } from '@material-ui/core/styles'; +import { BackstageTheme } from '@backstage/theme'; +import { Progress } from '@backstage/core'; +import { ComponentIdValidators } from '../../util/validate'; + +const useStyles = makeStyles(theme => ({ + form: { + alignItems: 'flex-start', + display: 'flex', + flexFlow: 'column nowrap', + }, + submit: { + marginTop: theme.spacing(1), + }, +})); + +type RegisterComponentProps = { + onSubmit: () => any; + submitting: boolean; +}; + +const RegisterComponentForm: FC = ({ + onSubmit, + submitting, +}) => { + const { register, handleSubmit, errors, formState } = useForm({ + mode: 'onChange', + }); + const classes = useStyles(); + const hasErrors = !!errors.componentIdInput; + const dirty = formState?.dirty; + if (submitting) { + return ( + <> + + Your component is being registered. Please wait. + + + + ); + } + return ( +
+ + + + {errors.componentIdInput && ( + + {errors.componentIdInput.message} + + )} + + +
+ ); +}; + +export default RegisterComponentForm; diff --git a/plugins/register-component/src/components/RegisterComponentForm/index.ts b/plugins/register-component/src/components/RegisterComponentForm/index.ts new file mode 100644 index 0000000000..beb0d00ecc --- /dev/null +++ b/plugins/register-component/src/components/RegisterComponentForm/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 { default } from './RegisterComponentForm'; diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx new file mode 100644 index 0000000000..ac2645135e --- /dev/null +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import mockFetch from 'jest-fetch-mock'; +import RegisterComponentPage from './RegisterComponentPage'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; + +describe('RegisterComponentPage', () => { + it('should render', () => { + mockFetch.mockResponse(() => new Promise(() => {})); + const rendered = render( + + + , + ); + expect(rendered.getByText('Register Component')).toBeInTheDocument(); + }); +}); diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx new file mode 100644 index 0000000000..114c37fe4c --- /dev/null +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx @@ -0,0 +1,65 @@ +/* + * 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, useState } from 'react'; +import { Grid } from '@material-ui/core'; +import { + InfoCard, + Page, + pageTheme, + Content, + ContentHeader, + SupportButton, +} from '@backstage/core'; +import RegisterComponentForm from '../RegisterComponentForm'; + +const RegisterComponentPage: FC<{}> = () => { + const [isSubmitting, setIsSubmitting] = useState(false); + + useEffect(() => { + if (isSubmitting) { + setTimeout(() => { + setIsSubmitting(false); + }, 4000); + } + }, [isSubmitting]); + + const onSubmit = () => { + setIsSubmitting(true); + }; + + return ( + + + + Documentation + + + + + + + + + + + ); +}; + +export default RegisterComponentPage; diff --git a/plugins/register-component/src/components/RegisterComponentPage/index.ts b/plugins/register-component/src/components/RegisterComponentPage/index.ts new file mode 100644 index 0000000000..e0757e0eee --- /dev/null +++ b/plugins/register-component/src/components/RegisterComponentPage/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 { default } from './RegisterComponentPage'; diff --git a/plugins/register-component/src/index.ts b/plugins/register-component/src/index.ts new file mode 100644 index 0000000000..3a0a0fe2d3 --- /dev/null +++ b/plugins/register-component/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { plugin } from './plugin'; diff --git a/plugins/register-component/src/plugin.test.ts b/plugins/register-component/src/plugin.test.ts new file mode 100644 index 0000000000..1e61060202 --- /dev/null +++ b/plugins/register-component/src/plugin.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 { plugin } from './plugin'; + +describe('register-component', () => { + it('should export plugin', () => { + expect(plugin).toBeDefined(); + }); +}); diff --git a/plugins/register-component/src/plugin.ts b/plugins/register-component/src/plugin.ts new file mode 100644 index 0000000000..f32e9dc84f --- /dev/null +++ b/plugins/register-component/src/plugin.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createPlugin } from '@backstage/core'; +import RegisterComponentPage from './components/RegisterComponentPage'; + +export const plugin = createPlugin({ + id: 'register-component', + register({ router }) { + router.registerRoute('/register-component', RegisterComponentPage); + }, +}); diff --git a/plugins/register-component/src/setupTests.ts b/plugins/register-component/src/setupTests.ts new file mode 100644 index 0000000000..1a907ab8e6 --- /dev/null +++ b/plugins/register-component/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. + */ + +import '@testing-library/jest-dom/extend-expect'; +require('jest-fetch-mock').enableMocks(); diff --git a/plugins/register-component/src/util/validate.test.ts b/plugins/register-component/src/util/validate.test.ts new file mode 100644 index 0000000000..43e05a4671 --- /dev/null +++ b/plugins/register-component/src/util/validate.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { ComponentIdValidators } from './validate'; + +describe('ComponentIdValidators', () => { + describe('httpsValidator validator', () => { + const errorMessage = 'Must start with https://.'; + test.each([ + [true, 'https://example.com'], + [errorMessage, 'http://example.com'], + [errorMessage, 'example.com'], + [errorMessage, 'www.example.com'], + [errorMessage, ''], + [errorMessage, undefined], + ])('should return %p for %s', (expected: string | boolean, arg: any) => { + expect(ComponentIdValidators.httpsValidator(arg)).toBe(expected); + }); + }); + describe('masterValidator', () => { + const errorMessage = 'Must reference a file on the master branch.'; + test.each([ + [true, '/blob/master/'], + [true, 'http://example.com/blob/master/'], + [errorMessage, 'blob/master/'], + [errorMessage, '/blob/master'], + [errorMessage, '/master/'], + [errorMessage, ''], + [errorMessage, undefined], + ])('should return %p for %s', (expected: string | boolean, arg: any) => { + expect(ComponentIdValidators.masterValidator(arg)).toBe(expected); + }); + }); + describe('yamlValidator', () => { + const errorMessage = "Must end with '.yaml'."; + test.each([ + [true, '.yaml'], + [true, 'http://example.com/blob/master/service.yaml'], + [true, 'https://example.yaml'], + [errorMessage, '.yml'], + [errorMessage, 'http://example.com/blob/master/service'], + [errorMessage, undefined], + ])('should return %p for %s', (expected: string | boolean, arg: any) => { + expect(ComponentIdValidators.yamlValidator(arg)).toBe(expected); + }); + }); +}); diff --git a/plugins/register-component/src/util/validate.ts b/plugins/register-component/src/util/validate.ts new file mode 100644 index 0000000000..175d9378df --- /dev/null +++ b/plugins/register-component/src/util/validate.ts @@ -0,0 +1,27 @@ +/* + * 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 ComponentIdValidators = { + httpsValidator: (value: any) => + (typeof value === 'string' && value.match(/^https:\/\//) !== null) || + 'Must start with https://.', + masterValidator: (value: any) => + (typeof value === 'string' && value.match(/\/blob\/master\//) !== null) || + 'Must reference a file on the master branch.', + yamlValidator: (value: any) => + (typeof value === 'string' && value.match(/.yaml$/) !== null) || + "Must end with '.yaml'.", +}; diff --git a/plugins/register-component/tsconfig.json b/plugins/register-component/tsconfig.json new file mode 100644 index 0000000000..b663b01fa2 --- /dev/null +++ b/plugins/register-component/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "dev"], + "compilerOptions": {} +} diff --git a/yarn.lock b/yarn.lock index 6b716af0dd..9f974135a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17772,6 +17772,11 @@ react-helmet@5.2.1: react-fast-compare "^2.0.2" react-side-effect "^1.1.0" +react-hook-form@^5.7.2: + version "5.7.2" + resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-5.7.2.tgz#a84e259e5d37dd30949af4f79c4dac31101b79ac" + integrity sha512-bJvY348vayIvEUmSK7Fvea/NgqbT2racA2IbnJz/aPlQ3GBtaTeDITH6rtCa6y++obZzG6E3Q8VuoXPir7QYUg== + react-hotkeys@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz#a7719c7340cbba888b0e9184f806a9ec0ac2c53f" From b9604720a6bbdd4897282594a8662903135ea052 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 13 May 2020 06:27:52 +0000 Subject: [PATCH 19/73] build(deps): bump react-router from 5.1.2 to 5.2.0 Bumps [react-router](https://github.com/ReactTraining/react-router) from 5.1.2 to 5.2.0. - [Release notes](https://github.com/ReactTraining/react-router/releases) - [Changelog](https://github.com/ReactTraining/react-router/blob/master/CHANGELOG.md) - [Commits](https://github.com/ReactTraining/react-router/compare/v5.1.2...v5.2.0) Signed-off-by: dependabot-preview[bot] --- yarn.lock | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6b716af0dd..7ce91a6a05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -949,14 +949,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.9.2" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" - integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/runtime@^7.9.6": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6": version "7.9.6" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== @@ -14643,6 +14636,14 @@ mini-create-react-context@^0.3.0: gud "^1.0.0" tiny-warning "^1.0.2" +mini-create-react-context@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040" + integrity sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA== + dependencies: + "@babel/runtime" "^7.5.5" + tiny-warning "^1.0.3" + mini-css-extract-plugin@0.9.0: version "0.9.0" resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" @@ -17857,7 +17858,7 @@ react-router-dom@5.1.2, react-router-dom@^5.1.2: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@5.1.2, react-router@^5.1.2: +react-router@5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== @@ -17873,6 +17874,22 @@ react-router@5.1.2, react-router@^5.1.2: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-router@^5.1.2: + version "5.2.0" + resolved "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293" + integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.4.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + react-scripts@^3.4.1: version "3.4.1" resolved "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a" @@ -20560,7 +20577,7 @@ tiny-relative-date@^1.3.0: resolved "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== -tiny-warning@^1.0.0, tiny-warning@^1.0.2: +tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== From 1e8ad4b1631b1dc756e19d7fa84162f32966d550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 13 May 2020 08:34:08 +0200 Subject: [PATCH 20/73] Bump TS to 3.9 --- packages/backend-common/package.json | 2 +- packages/backend/package.json | 2 +- .../{{cookiecutter.componentId}}/package.json | 2 +- yarn.lock | 7 ++++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index eec21063ce..617c29105b 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -39,7 +39,7 @@ "jest": "^25.1.0", "jest-fetch-mock": "^3.0.3", "supertest": "^4.0.2", - "typescript": "^3.8.3" + "typescript": "^3.9.2" }, "files": [ "dist" diff --git a/packages/backend/package.json b/packages/backend/package.json index 10ec8eb6bb..14668ef3f3 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -36,7 +36,7 @@ "@types/helmet": "^0.0.45", "jest": "^25.1.0", "tsc-watch": "^4.2.3", - "typescript": "^3.8.3", + "typescript": "^3.9.2", "winston": "^3.2.1" }, "nodemonConfig": { diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/package.json b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/package.json index cf9d469e39..a0ae26e267 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/package.json +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.componentId}}/package.json @@ -30,7 +30,7 @@ "@types/styled-components": "^4.1.18", "husky": "^2.7.0", "jest-junit": "^8.0.0", - "typescript": "^3.4.5" + "typescript": "^3.9.2" }, "husky": { "hooks": { diff --git a/yarn.lock b/yarn.lock index 9f974135a2..f5574f112f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20892,11 +20892,16 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.7.4, typescript@^3.8.3: +typescript@^3.7.4: version "3.8.3" resolved "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== +typescript@^3.9.2: + version "3.9.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.2.tgz#64e9c8e9be6ea583c54607677dd4680a1cf35db9" + integrity sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw== + 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" From b103a16c88d59f06910c986a3439351df9f8f32c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 13 May 2020 09:27:47 +0200 Subject: [PATCH 21/73] build(deps): bump @lerna/project from 3.18.0 to 3.21.0 (#840) Bumps [@lerna/project](https://github.com/lerna/lerna/tree/HEAD/core/project) from 3.18.0 to 3.21.0. - [Release notes](https://github.com/lerna/lerna/releases) - [Changelog](https://github.com/lerna/lerna/blob/master/core/project/CHANGELOG.md) - [Commits](https://github.com/lerna/lerna/commits/v3.21.0/core/project) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- yarn.lock | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index f5574f112f..8dd7bf52f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2245,7 +2245,7 @@ npmlog "^4.1.2" upath "^1.2.0" -"@lerna/project@3.18.0", "@lerna/project@^3.18.0": +"@lerna/project@3.18.0": version "3.18.0" resolved "https://registry.npmjs.org/@lerna/project/-/project-3.18.0.tgz#56feee01daeb42c03cbdf0ed8a2a10cbce32f670" integrity sha512-+LDwvdAp0BurOAWmeHE3uuticsq9hNxBI0+FMHiIai8jrygpJGahaQrBYWpwbshbQyVLeQgx3+YJdW2TbEdFWA== @@ -2263,6 +2263,24 @@ resolve-from "^4.0.0" write-json-file "^3.2.0" +"@lerna/project@^3.18.0": + version "3.21.0" + resolved "https://registry.npmjs.org/@lerna/project/-/project-3.21.0.tgz#5d784d2d10c561a00f20320bcdb040997c10502d" + integrity sha512-xT1mrpET2BF11CY32uypV2GPtPVm6Hgtha7D81GQP9iAitk9EccrdNjYGt5UBYASl4CIDXBRxwmTTVGfrCx82A== + dependencies: + "@lerna/package" "3.16.0" + "@lerna/validation-error" "3.13.0" + cosmiconfig "^5.1.0" + dedent "^0.7.0" + dot-prop "^4.2.0" + glob-parent "^5.0.0" + globby "^9.2.0" + load-json-file "^5.3.0" + npmlog "^4.1.2" + p-map "^2.1.0" + resolve-from "^4.0.0" + write-json-file "^3.2.0" + "@lerna/prompt@3.18.5": version "3.18.5" resolved "https://registry.npmjs.org/@lerna/prompt/-/prompt-3.18.5.tgz#628cd545f225887d060491ab95df899cfc5218a1" From 930752a453e5ad29181e2f020bf98eb99ea2932d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 13 May 2020 07:29:35 +0000 Subject: [PATCH 22/73] build(deps-dev): bump @types/helmet from 0.0.45 to 0.0.47 Bumps [@types/helmet](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/helmet) from 0.0.45 to 0.0.47. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/helmet) Signed-off-by: dependabot-preview[bot] --- packages/backend/package.json | 2 +- yarn.lock | 19 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index 14668ef3f3..23cd0f05ba 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -33,7 +33,7 @@ "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", - "@types/helmet": "^0.0.45", + "@types/helmet": "^0.0.47", "jest": "^25.1.0", "tsc-watch": "^4.2.3", "typescript": "^3.9.2", diff --git a/yarn.lock b/yarn.lock index f5574f112f..23e25c9025 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4031,16 +4031,7 @@ "@types/node" "*" "@types/range-parser" "*" -"@types/express@*": - version "4.17.3" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz#38e4458ce2067873b09a73908df488870c303bd9" - integrity sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "*" - "@types/serve-static" "*" - -"@types/express@^4.17.6": +"@types/express@*", "@types/express@^4.17.6": version "4.17.6" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz#6bce49e49570507b86ea1b07b806f04697fac45e" integrity sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w== @@ -4071,10 +4062,10 @@ resolved "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.7.2.tgz#cd8a360c193ce4d672575a20a79f49ba036d38d2" integrity sha512-ifFemzjNchFBCtHS6bZNhSZCBu7tbtOe0e8qY0z2J4HtFXmPJjm6fXSaQsTG7yhShBEZtt2oP/bkwu5k+emlkQ== -"@types/helmet@^0.0.45": - version "0.0.45" - resolved "https://registry.npmjs.org/@types/helmet/-/helmet-0.0.45.tgz#3eab6550a4e19acf86012596a7f1981529480fd5" - integrity sha512-PsLZI1NqKpXvsMZxh66xAZtpKiTeW+swY8a8LnCNSBbM/mvwU41P3BYoEqkJM9RbITPsq4uhIH0NkIsL9fzPbg== +"@types/helmet@^0.0.47": + version "0.0.47" + resolved "https://registry.npmjs.org/@types/helmet/-/helmet-0.0.47.tgz#ec5161541b649142205b7c558bca14801c5ce129" + integrity sha512-TcHA/djjdUtrMtq/QAayVLrsgjNNZ1Uhtz0KhfH01mrmjH44E54DA1A0HNbwW0H/NBFqV+tGMo85ACuEhMXcdg== dependencies: "@types/express" "*" From 9114a08081856bfa1cec63132a2c5b17aeee9964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 13 May 2020 10:34:53 +0200 Subject: [PATCH 23/73] Rearrange ingestion and descriptor parsing --- packages/backend-common/package.json | 1 + .../catalog-backend/fixtures/component1.yaml | 7 ++- .../src/catalog/DatabaseCatalog.ts | 36 +----------- plugins/catalog-backend/src/catalog/types.ts | 6 -- .../src/descriptors/component.ts | 56 +++++++++++++++++++ .../src/descriptors/envelope.ts | 53 ++++++++++++++++++ .../catalog-backend/src/descriptors/index.ts | 19 +++++++ .../catalog-backend/src/descriptors/util.ts | 25 +++++++++ .../src/ingestion/fileLocation.ts | 34 +++++++++++ .../catalog-backend/src/ingestion/index.ts | 18 ++++++ plugins/catalog-backend/src/ingestion/util.ts | 27 +++++++++ yarn.lock | 21 ++++--- 12 files changed, 255 insertions(+), 48 deletions(-) create mode 100644 plugins/catalog-backend/src/descriptors/component.ts create mode 100644 plugins/catalog-backend/src/descriptors/envelope.ts create mode 100644 plugins/catalog-backend/src/descriptors/index.ts create mode 100644 plugins/catalog-backend/src/descriptors/util.ts create mode 100644 plugins/catalog-backend/src/ingestion/fileLocation.ts create mode 100644 plugins/catalog-backend/src/ingestion/index.ts create mode 100644 plugins/catalog-backend/src/ingestion/util.ts diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 617c29105b..7a8886adc3 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -34,6 +34,7 @@ "@types/http-errors": "^1.6.3", "@types/morgan": "^1.9.0", "@types/supertest": "^2.0.8", + "@types/yaml": "^1.9.7", "get-port": "^5.1.1", "http-errors": "^1.7.3", "jest": "^25.1.0", diff --git a/plugins/catalog-backend/fixtures/component1.yaml b/plugins/catalog-backend/fixtures/component1.yaml index 8db5cfc851..b0b54e0902 100644 --- a/plugins/catalog-backend/fixtures/component1.yaml +++ b/plugins/catalog-backend/fixtures/component1.yaml @@ -1 +1,6 @@ -name: Component1 +apiVersion: catalog.backstage.io/v1 +kind: Component +metadata: + name: component1 +spec: + type: service diff --git a/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts index d28278c496..51cccf2286 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts @@ -17,42 +17,10 @@ import { NotFoundError } from '@backstage/backend-common'; import Knex from 'knex'; import path from 'path'; -import fs from 'fs-extra'; import { v4 as uuidv4 } from 'uuid'; import { Logger } from 'winston'; -import { - AddLocationRequest, - Catalog, - Component, - Location, - componentShape, -} from './types'; -import YAML from 'yaml'; - -async function readLocation(location: Location): Promise { - switch (location.type) { - case 'file': { - let parsed; - try { - const raw = await fs.readFile(location.target, 'utf8'); - parsed = YAML.parse(raw); - } catch (e) { - throw new Error(`Unable to read "${location.target}", ${e}`); - } - - try { - return [await componentShape.validate(parsed, { strict: true })]; - } catch (e) { - throw new Error( - `Malformed file contents at "${location.target}", ${e}`, - ); - } - } - - default: - throw new Error(`Unknown type "${location.type}"`); - } -} +import { readLocation } from '../ingestion'; +import { AddLocationRequest, Catalog, Component, Location } from './types'; export class DatabaseCatalog implements Catalog { static async create( diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index abac94345d..e91c5f8267 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -20,12 +20,6 @@ export type Component = { name: string; }; -export const componentShape: yup.Schema = yup - .object({ - name: yup.string().required(), - }) - .unknown(); - export type Location = { id: string; type: string; diff --git a/plugins/catalog-backend/src/descriptors/component.ts b/plugins/catalog-backend/src/descriptors/component.ts new file mode 100644 index 0000000000..cb0c30aa61 --- /dev/null +++ b/plugins/catalog-backend/src/descriptors/component.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as yup from 'yup'; +import { DescriptorEnvelope } from './envelope'; +import { Component } from '../catalog/types'; + +export type ComponentDescriptor = { + metadata: { + name: string; + }; + spec: { + type: string; + }; +}; + +const componentDescriptorSchema: yup.Schema = yup.object({ + metadata: yup.object({ + name: yup.string().required(), + }), + spec: yup.object({ + type: yup.string().required(), + }), +}); + +export async function parseComponentDescriptor( + envelope: DescriptorEnvelope, +): Promise { + let componentDescriptor; + try { + componentDescriptor = await componentDescriptorSchema.validate(envelope, { + strict: true, + }); + } catch (e) { + throw new Error(`Malformed component, ${e}`); + } + + const component: Component = { + name: componentDescriptor.metadata.name, + }; + + return [component]; +} diff --git a/plugins/catalog-backend/src/descriptors/envelope.ts b/plugins/catalog-backend/src/descriptors/envelope.ts new file mode 100644 index 0000000000..dd78d860ee --- /dev/null +++ b/plugins/catalog-backend/src/descriptors/envelope.ts @@ -0,0 +1,53 @@ +/* + * 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 yaml from 'yaml'; +import * as yup from 'yup'; + +export type DescriptorEnvelope = { + apiVersion: string; + kind: string; + metadata?: object; + spec?: object; +}; + +const descriptorEnvelopeSchema: yup.Schema = yup + .object({ + apiVersion: yup.string().required(), + kind: yup.string().required(), + metadata: yup.object(), + spec: yup.object(), + }) + .noUnknown(); + +export async function parseDescriptorEnvelope( + rawYaml: string, +): Promise { + let descriptor; + try { + descriptor = yaml.parse(rawYaml); + } catch (e) { + throw new Error(`Malformed YAML, ${e}`); + } + + try { + return await descriptorEnvelopeSchema.validate(descriptor, { + strict: true, + }); + } catch (e) { + throw new Error(`Malformed envelope, ${e}`); + } +} diff --git a/plugins/catalog-backend/src/descriptors/index.ts b/plugins/catalog-backend/src/descriptors/index.ts new file mode 100644 index 0000000000..db3fd4c938 --- /dev/null +++ b/plugins/catalog-backend/src/descriptors/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. + */ + +export * from './component'; +export * from './envelope'; +export * from './util'; diff --git a/plugins/catalog-backend/src/descriptors/util.ts b/plugins/catalog-backend/src/descriptors/util.ts new file mode 100644 index 0000000000..e7b4944215 --- /dev/null +++ b/plugins/catalog-backend/src/descriptors/util.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component } from '../catalog/types'; +import { parseComponentDescriptor } from './component'; +import { parseDescriptorEnvelope } from './envelope'; + +// TODO(freben): Temporary helper that ignores the kind +export async function parseDescriptor(rawYaml: string): Promise { + const env = await parseDescriptorEnvelope(rawYaml); + return await parseComponentDescriptor(env); +} diff --git a/plugins/catalog-backend/src/ingestion/fileLocation.ts b/plugins/catalog-backend/src/ingestion/fileLocation.ts new file mode 100644 index 0000000000..524721035e --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/fileLocation.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs-extra'; +import { parseDescriptor } from '../descriptors'; +import { Component } from '../catalog/types'; + +export async function readFileLocation(target: string): Promise { + let rawYaml; + try { + rawYaml = await fs.readFile(target, 'utf8'); + } catch (e) { + throw new Error(`Unable to read "${target}", ${e}`); + } + + try { + return parseDescriptor(rawYaml); + } catch (e) { + throw new Error(`Malformed descriptor at "${target}", ${e}`); + } +} diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts new file mode 100644 index 0000000000..775fe29f84 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './fileLocation'; +export * from './util'; diff --git a/plugins/catalog-backend/src/ingestion/util.ts b/plugins/catalog-backend/src/ingestion/util.ts new file mode 100644 index 0000000000..91b7ad2678 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/util.ts @@ -0,0 +1,27 @@ +/* + * 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 { Component, Location } from '../catalog/types'; +import { readFileLocation } from './fileLocation'; + +export async function readLocation(location: Location): Promise { + switch (location.type) { + case 'file': + return await readFileLocation(location.target); + default: + throw new Error(`Unknown type "${location.type}"`); + } +} diff --git a/yarn.lock b/yarn.lock index 4168fce334..d312e5b03b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4602,6 +4602,13 @@ "@types/webpack-sources" "*" source-map "^0.6.0" +"@types/yaml@^1.9.7": + version "1.9.7" + resolved "https://registry.npmjs.org/@types/yaml/-/yaml-1.9.7.tgz#2331f36e0aac91311a63d33eb026c21687729679" + integrity sha512-8WMXRDD1D+wCohjfslHDgICd2JtMATZU8CkhH8LVJqcJs6dyYj5TGptzP8wApbmEullGBSsCEzzap73DQ1HJaA== + dependencies: + yaml "*" + "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -22129,6 +22136,13 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@*, yaml@^1.9.2: + version "1.9.2" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz#f0cfa865f003ab707663e4f04b3956957ea564ed" + integrity sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg== + dependencies: + "@babel/runtime" "^7.9.2" + yaml@^1.7.2: version "1.8.3" resolved "https://registry.npmjs.org/yaml/-/yaml-1.8.3.tgz#2f420fca58b68ce3a332d0ca64be1d191dd3f87a" @@ -22136,13 +22150,6 @@ yaml@^1.7.2: dependencies: "@babel/runtime" "^7.8.7" -yaml@^1.9.2: - version "1.9.2" - resolved "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz#f0cfa865f003ab707663e4f04b3956957ea564ed" - integrity sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg== - dependencies: - "@babel/runtime" "^7.9.2" - yargs-parser@^10.0.0: version "10.1.0" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" From 1713fac765c7f9b7f8f14811903ac7a543eef45b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 13 May 2020 13:56:56 +0200 Subject: [PATCH 24/73] packages,plugins: bump react-router-dom to 5.2.0 --- packages/app/package.json | 2 +- .../default-app/packages/app/package.json.hbs | 2 +- .../plugins/welcome/package.json.hbs | 2 +- packages/core/package.json | 4 +- packages/dev-utils/package.json | 4 +- packages/test-utils/package.json | 4 +- plugins/graphiql/package.json | 2 +- plugins/home-page/package.json | 2 +- plugins/lighthouse/package.json | 2 +- plugins/welcome/package.json | 2 +- yarn.lock | 39 ++++--------------- 11 files changed, 20 insertions(+), 45 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 255f66e5e2..60b6e9529d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -20,7 +20,7 @@ "prop-types": "^15.7.2", "react": "^16.12.0", "react-dom": "^16.12.0", - "react-router-dom": "^5.1.2", + "react-router-dom": "^5.2.0", "react-use": "^13.24.0", "zen-observable": "^0.8.15" }, diff --git a/packages/cli/templates/default-app/packages/app/package.json.hbs b/packages/cli/templates/default-app/packages/app/package.json.hbs index 6956134a3c..5f4459f556 100644 --- a/packages/cli/templates/default-app/packages/app/package.json.hbs +++ b/packages/cli/templates/default-app/packages/app/package.json.hbs @@ -12,7 +12,7 @@ "plugin-welcome": "0.0.0", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-router-dom": "^5.1.2", + "react-router-dom": "^5.2.0", "react-use": "^13.24.0" }, "devDependencies": { diff --git a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs index 13fe34f682..194e81da25 100644 --- a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs +++ b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs @@ -20,7 +20,7 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "react-use": "^13.24.0", - "react-router-dom": "5.1.2" + "react-router-dom": "^5.2.0" }, "devDependencies": { "@backstage/cli": "^{{version}}", diff --git a/packages/core/package.json b/packages/core/package.json index a08cf12a29..c412219d98 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -38,8 +38,8 @@ "react-addons-text-content": "0.0.4", "react-dom": "^16.12.0", "react-helmet": "5.2.1", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", "react-sparklines": "^1.7.0", "react-syntax-highlighter": "^12.2.1" }, diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index bafbab2e32..77809cb44d 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -38,8 +38,8 @@ "@types/node": "^12.0.0", "react": "^16.12.0", "react-dom": "^16.12.0", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2" + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0" }, "files": [ "dist" diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index e34a750ae0..9cfc3cb2d5 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -36,8 +36,8 @@ "@types/node": "^12.0.0", "@testing-library/jest-dom": "^4.2.4", "react-dom": "^16.12.0", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2" + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0" }, "files": [ "dist" diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 3740ac01a2..d797254a98 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -49,7 +49,7 @@ "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3", - "react-router-dom": "^5.1.2" + "react-router-dom": "^5.2.0" }, "files": [ "dist" diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json index 121bed61da..bfd0d7c1b4 100644 --- a/plugins/home-page/package.json +++ b/plugins/home-page/package.json @@ -32,7 +32,7 @@ "@types/jest": "^24.0.0", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "react-router-dom": "^5.1.2", + "react-router-dom": "^5.2.0", "jest-fetch-mock": "^3.0.3" }, "files": [ diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 22073de3a1..28df6f9d9b 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -22,7 +22,7 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "react-markdown": "^4.3.1", - "react-router-dom": "^5.1.2", + "react-router-dom": "^5.2.0", "react-use": "^13.24.0" }, "devDependencies": { diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 4898d6b07d..dca61e691d 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -21,7 +21,7 @@ "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-router-dom": "5.1.2", + "react-router-dom": "^5.2.0", "react-use": "^13.24.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index d312e5b03b..b2840e55e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -949,7 +949,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6": version "7.9.6" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== @@ -14652,15 +14652,6 @@ min-indent@^1.0.0: resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256" integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY= -mini-create-react-context@^0.3.0: - version "0.3.2" - resolved "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" - integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== - dependencies: - "@babel/runtime" "^7.4.0" - gud "^1.0.0" - tiny-warning "^1.0.2" - mini-create-react-context@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040" @@ -17875,36 +17866,20 @@ react-redux@^7.0.3: prop-types "^15.7.2" react-is "^16.9.0" -react-router-dom@5.1.2, react-router-dom@^5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" - integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== +react-router-dom@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" + integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA== dependencies: "@babel/runtime" "^7.1.2" history "^4.9.0" loose-envify "^1.3.1" prop-types "^15.6.2" - react-router "5.1.2" + react-router "5.2.0" tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" - integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== - dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - mini-create-react-context "^0.3.0" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-router@^5.1.2: +react-router@5.2.0, react-router@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293" integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw== From f577f50fdcde687b95d086c297bd2504bfa3e3c8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 13 May 2020 13:50:52 +0200 Subject: [PATCH 25/73] github/workflows: run cli workflow on yarn.lock changes --- .github/workflows/cli.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 0dd8036e84..10330976d7 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -7,6 +7,7 @@ on: - 'packages/cli/**' - 'packages/core/**' - 'scripts/**' + - 'yarn.lock' jobs: build: From 1579a9dded713a797b62fe8c6bc116f6a51af3ba Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 13 May 2020 14:28:30 +0200 Subject: [PATCH 26/73] github/workflows: run all tests on changes to yarn.lock --- .github/workflows/frontend.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 15fb2b1fc7..f62f439119 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -48,6 +48,11 @@ jobs: with: node-version: ${{ matrix.node-version }} + - name: check for yarn.lock changes + id: yarn-lock + run: git diff --quiet origin/master HEAD -- yarn.lock + continue-on-error: true + - name: yarn install run: yarn install --frozen-lockfile @@ -57,9 +62,14 @@ jobs: - name: build run: yarn build - - name: test + - name: test changed packages + if: ${{ steps.yarn-lock.outcome == 'success' }} run: yarn lerna -- run test --since origin/master -- --coverage + - name: test all packages + if: ${{ steps.yarn-lock.outcome == 'failure' }} + run: yarn lerna -- run test -- --coverage + - name: yarn bundle, if app was changed run: git diff --quiet origin/master HEAD -- yarn.lock packages/app packages/core || yarn bundle From ab5c7c533cdf3bcf96ffdd196f48170a6a1b668f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 13 May 2020 12:29:29 +0000 Subject: [PATCH 27/73] build(deps): bump @types/jest from 24.9.1 to 25.2.1 Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 24.9.1 to 25.2.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest) Signed-off-by: dependabot-preview[bot] --- packages/app/package.json | 2 +- packages/core/package.json | 2 +- packages/dev-utils/package.json | 2 +- packages/test-utils-core/package.json | 2 +- packages/test-utils/package.json | 2 +- plugins/catalog/package.json | 2 +- plugins/explore/package.json | 2 +- plugins/graphiql/package.json | 2 +- plugins/home-page/package.json | 2 +- plugins/lighthouse/package.json | 2 +- plugins/register-component/package.json | 2 +- plugins/scaffolder/package.json | 2 +- plugins/tech-radar/package.json | 2 +- plugins/welcome/package.json | 2 +- yarn.lock | 68 ++++++++++++++----------- 15 files changed, 52 insertions(+), 44 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 60b6e9529d..45d089c3db 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -30,7 +30,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/react-router-dom": "^5.1.3", "@types/zen-observable": "^0.8.0", diff --git a/packages/core/package.json b/packages/core/package.json index c412219d98..475496e633 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -52,7 +52,7 @@ "@testing-library/user-event": "^7.1.2", "@types/classnames": "^2.2.9", "@types/google-protobuf": "^3.7.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/react-helmet": "^5.0.15", "@types/react-sparklines": "^1.7.0" diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 77809cb44d..1dcf605a36 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -34,7 +34,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "react": "^16.12.0", "react-dom": "^16.12.0", diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index 33c27dba62..af73688612 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -27,7 +27,7 @@ "dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "react": "^16.12.0", "react-dom": "^16.12.0" diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 9cfc3cb2d5..e70fa7c8d4 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -32,7 +32,7 @@ "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "react": "^16.12.0", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@testing-library/jest-dom": "^4.2.4", "react-dom": "^16.12.0", diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 2e786d9a3c..2ecade33d5 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -28,7 +28,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index c7a377c589..93a560e830 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -30,7 +30,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index d797254a98..56872de2db 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -45,7 +45,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3", diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json index bfd0d7c1b4..134e8e387b 100644 --- a/plugins/home-page/package.json +++ b/plugins/home-page/package.json @@ -29,7 +29,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "react-router-dom": "^5.2.0", diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 28df6f9d9b..49d97390a0 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -31,7 +31,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3", diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 9113006ad7..a851be6ba4 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -30,7 +30,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3" diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 26b9fcdb17..0278aa5970 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -28,7 +28,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index aaa3aaf7c2..d45c14a692 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -34,7 +34,7 @@ "@testing-library/user-event": "^7.1.2", "@types/color": "^3.0.1", "@types/d3-force": "^1.2.1", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index dca61e691d..d1d49f4c08 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -29,7 +29,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3", diff --git a/yarn.lock b/yarn.lock index b2840e55e3..4ec8626db9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1758,6 +1758,16 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" +"@jest/types@^25.5.0": + version "25.5.0" + resolved "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" + integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + "@lerna/add@3.20.0": version "3.20.0" resolved "https://registry.npmjs.org/@lerna/add/-/add-3.20.0.tgz#bea7edf36fc93fb72ec34cb9ba854c48d4abf309" @@ -4166,20 +4176,13 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@types/jest@*", "@types/jest@^25.1.0": - version "25.1.4" - resolved "https://registry.npmjs.org/@types/jest/-/jest-25.1.4.tgz#9e9f1e59dda86d3fd56afce71d1ea1b331f6f760" - integrity sha512-QDDY2uNAhCV7TMCITrxz+MRk1EizcsevzfeS6LykIlq2V1E5oO4wXG8V2ZEd9w7Snxeeagk46YbMgZ8ESHx3sw== +"@types/jest@*", "@types/jest@^25.1.0", "@types/jest@^25.2.1": + version "25.2.1" + resolved "https://registry.npmjs.org/@types/jest/-/jest-25.2.1.tgz#9544cd438607955381c1bdbdb97767a249297db5" + integrity sha512-msra1bCaAeEdkSyA0CZ6gW1ukMIvZ5YoJkdXw/qhQdsuuDlFTcEUrUw8CLCPt2rVRUfXlClVvK2gvPs9IokZaA== dependencies: - jest-diff "^25.1.0" - pretty-format "^25.1.0" - -"@types/jest@^24.0.0": - version "24.9.1" - resolved "https://registry.npmjs.org/@types/jest/-/jest-24.9.1.tgz#02baf9573c78f1b9974a5f36778b366aa77bd534" - integrity sha512-Fb38HkXSVA4L8fGKEZ6le5bB8r6MRWlOCZbVuWZcmOMSCd2wCYOwN1ibj8daIoV9naq7aaOZjrLCoCMptKU/4Q== - dependencies: - jest-diff "^24.3.0" + jest-diff "^25.2.1" + pretty-format "^25.2.1" "@types/jquery@*": version "3.3.35" @@ -8413,10 +8416,10 @@ diff-sequences@^24.9.0: resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== -diff-sequences@^25.1.0: - version "25.1.0" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.1.0.tgz#fd29a46f1c913fd66c22645dc75bffbe43051f32" - integrity sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw== +diff-sequences@^25.2.6: + version "25.2.6" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" + integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== diff@^4.0.1, diff@^4.0.2: version "4.0.2" @@ -12454,7 +12457,7 @@ jest-css-modules@^2.1.0: dependencies: identity-obj-proxy "3.0.0" -jest-diff@^24.0.0, jest-diff@^24.3.0, jest-diff@^24.9.0: +jest-diff@^24.0.0, jest-diff@^24.9.0: version "24.9.0" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== @@ -12464,15 +12467,15 @@ jest-diff@^24.0.0, jest-diff@^24.3.0, jest-diff@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" -jest-diff@^25.1.0: - version "25.1.0" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-25.1.0.tgz#58b827e63edea1bc80c1de952b80cec9ac50e1ad" - integrity sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw== +jest-diff@^25.1.0, jest-diff@^25.2.1: + version "25.5.0" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" + integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== dependencies: chalk "^3.0.0" - diff-sequences "^25.1.0" - jest-get-type "^25.1.0" - pretty-format "^25.1.0" + diff-sequences "^25.2.6" + jest-get-type "^25.2.6" + pretty-format "^25.5.0" jest-docblock@^24.3.0: version "24.9.0" @@ -12594,6 +12597,11 @@ jest-get-type@^25.1.0: resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.1.0.tgz#1cfe5fc34f148dc3a8a3b7275f6b9ce9e2e8a876" integrity sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw== +jest-get-type@^25.2.6: + version "25.2.6" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" + integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== + jest-haste-map@^24.9.0: version "24.9.0" resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" @@ -17221,12 +17229,12 @@ pretty-format@^24.0.0, pretty-format@^24.3.0, pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" -pretty-format@^25.1.0: - version "25.1.0" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-25.1.0.tgz#ed869bdaec1356fc5ae45de045e2c8ec7b07b0c8" - integrity sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ== +pretty-format@^25.1.0, pretty-format@^25.2.1, pretty-format@^25.5.0: + version "25.5.0" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" + integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ== dependencies: - "@jest/types" "^25.1.0" + "@jest/types" "^25.5.0" ansi-regex "^5.0.0" ansi-styles "^4.0.0" react-is "^16.12.0" From 78c3c08de7149b021fbcc44b5c62285287aaa036 Mon Sep 17 00:00:00 2001 From: KoukiNishihara Date: Wed, 13 May 2020 03:05:27 +0900 Subject: [PATCH 28/73] Delete animation --- packages/core/src/components/Status/Status.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/components/Status/Status.tsx b/packages/core/src/components/Status/Status.tsx index b34f8a3c31..189b32475c 100644 --- a/packages/core/src/components/Status/Status.tsx +++ b/packages/core/src/components/Status/Status.tsx @@ -54,7 +54,6 @@ const useStyles = makeStyles((theme) => ({ running: { '&::before': { backgroundColor: theme.palette.status.running, - animation: '$blink 0.8s step-start 0s infinite', }, }, aborted: { From 5753652dd4f00b756d2f9d1580dd541950ad6009 Mon Sep 17 00:00:00 2001 From: KoukiNishihara Date: Wed, 13 May 2020 03:26:11 +0900 Subject: [PATCH 29/73] Refactor background on animation --- packages/core/src/components/Status/Status.tsx | 5 ----- packages/theme/src/themes.ts | 2 -- packages/theme/src/types.ts | 1 - 3 files changed, 8 deletions(-) diff --git a/packages/core/src/components/Status/Status.tsx b/packages/core/src/components/Status/Status.tsx index 189b32475c..9d8450293f 100644 --- a/packages/core/src/components/Status/Status.tsx +++ b/packages/core/src/components/Status/Status.tsx @@ -61,11 +61,6 @@ const useStyles = makeStyles((theme) => ({ backgroundColor: theme.palette.status.aborted, }, }, - '@keyframes blink': { - '50%': { - backgroundColor: theme.palette.status.background, - }, - }, })); export const StatusOK: FC<{}> = (props) => { diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts index 86fb5a1982..33cefe3283 100644 --- a/packages/theme/src/themes.ts +++ b/packages/theme/src/themes.ts @@ -29,7 +29,6 @@ export const lightTheme = createTheme({ running: '#2E77D0', pending: '#FFED51', aborted: '#757575', - background: '#F8F8F8', }, bursts: { fontColor: '#FEFEFE', @@ -70,7 +69,6 @@ export const darkTheme = createTheme({ running: '#2E77D0', pending: '#FFED51', aborted: '#757575', - background: '#F8F8F8', }, bursts: { fontColor: '#FEFEFE', diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index 54bca9d61b..82c717e433 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -28,7 +28,6 @@ type PaletteAdditions = { pending: string; running: string; aborted: string; - background: string; }; border: string; textContrast: string; From 29abee4c3165b8604dbcaf04679ebfaebe599427 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 13 May 2020 12:09:27 +0200 Subject: [PATCH 30/73] packages/cli: fix create-app package resolution in e2e test mode --- .../cli/src/commands/create-app/createApp.ts | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/commands/create-app/createApp.ts b/packages/cli/src/commands/create-app/createApp.ts index 2a9f6ff9fd..bfb78762d9 100644 --- a/packages/cli/src/commands/create-app/createApp.ts +++ b/packages/cli/src/commands/create-app/createApp.ts @@ -88,25 +88,39 @@ export async function moveApp( async function addPackageResolutions(appDir: string) { const pkgJsonPath = resolvePath(appDir, 'package.json'); - const packageFileContent = await fs.readFile(pkgJsonPath, 'utf-8'); - const packageFileJson = JSON.parse(packageFileContent); + const pkgJson = await fs.readJson(pkgJsonPath); - packageFileJson.resolutions = packageFileJson.resolutions || {}; + pkgJson.resolutions = pkgJson.resolutions || {}; + pkgJson.dependencies = pkgJson.dependencies || {}; - const packages = ['cli', 'core', 'test-utils', 'test-utils-core', 'theme']; + const depNames = [ + 'cli', + 'core', + 'dev-utils', + 'test-utils', + 'test-utils-core', + 'theme', + ]; - for (const pkg of packages) { - await Task.forItem('adding', `${pkg} link to package.json`, async () => { - const pkgPath = paths.resolveOwnRoot('packages', pkg); - packageFileJson.resolutions[`@backstage/${pkg}`] = `file:${pkgPath}`; - const newContents = `${JSON.stringify(packageFileJson, null, 2)}\n`; + for (const name of depNames) { + await Task.forItem( + 'adding', + `@backstage/${name} link to package.json`, + async () => { + const pkgPath = paths.resolveOwnRoot('packages', name); + // Add to both resolutions and dependencies, or transitive dependencies will still be fetched from the registry. + pkgJson.dependencies[`@backstage/${name}`] = `file:${pkgPath}`; + pkgJson.resolutions[`@backstage/${name}`] = `file:${pkgPath}`; - await fs.writeFile(pkgJsonPath, newContents, 'utf-8').catch((error) => { - throw new Error( - `Failed to add resolutions to package.json: ${error.message}`, - ); - }); - }); + await fs + .writeJSON(pkgJsonPath, pkgJson, { encoding: 'utf8', spaces: 2 }) + .catch((error) => { + throw new Error( + `Failed to add resolutions to package.json: ${error.message}`, + ); + }); + }, + ); } } From f298700ba98bb6c62796c4a008aa1bd803b23eeb Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 3 May 2020 17:44:48 +0200 Subject: [PATCH 31/73] packages/core: added basic nav target implementation --- packages/core/src/api/index.ts | 1 + packages/core/src/api/navTargets/NavTarget.ts | 43 +++++++++++++++++++ packages/core/src/api/navTargets/index.ts | 18 ++++++++ packages/core/src/api/navTargets/types.ts | 35 +++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 packages/core/src/api/navTargets/NavTarget.ts create mode 100644 packages/core/src/api/navTargets/index.ts create mode 100644 packages/core/src/api/navTargets/types.ts diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts index e60a7efa18..8c8972c72c 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -16,5 +16,6 @@ export * from './api'; export * from './apis'; +export * from './navTargets'; export { FeatureFlags } from './app/FeatureFlags'; export { useApp } from './app/AppContext'; diff --git a/packages/core/src/api/navTargets/NavTarget.ts b/packages/core/src/api/navTargets/NavTarget.ts new file mode 100644 index 0000000000..ee964abb01 --- /dev/null +++ b/packages/core/src/api/navTargets/NavTarget.ts @@ -0,0 +1,43 @@ +/* + * 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 type { NavTargetConfig, NavTargetOverrideConfig } from './types'; + +export class MutableNavTarget { + private effectiveConfig: NavTargetConfig = this.config; + + constructor(private readonly config: NavTargetConfig) {} + + override(overrideConfig: NavTargetOverrideConfig) { + this.effectiveConfig = { ...this.config, ...overrideConfig }; + } + + get icon() { + return this.effectiveConfig.icon; + } + + get path() { + return this.effectiveConfig.path; + } + + get title() { + return this.effectiveConfig.title; + } +} + +export function createNavTarget(config: NavTargetConfig): MutableNavTarget { + return new MutableNavTarget(config); +} diff --git a/packages/core/src/api/navTargets/index.ts b/packages/core/src/api/navTargets/index.ts new file mode 100644 index 0000000000..6c2c899f89 --- /dev/null +++ b/packages/core/src/api/navTargets/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type { NavTarget, NavTargetConfig, NavTargetOverrideConfig } from './types'; +export { createNavTarget } from './NavTarget'; diff --git a/packages/core/src/api/navTargets/types.ts b/packages/core/src/api/navTargets/types.ts new file mode 100644 index 0000000000..104b8203b0 --- /dev/null +++ b/packages/core/src/api/navTargets/types.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IconComponent } from '../../icons'; + +export type NavTarget = { + path: string; + icon: IconComponent; + title: string; +}; + +export type NavTargetConfig = { + path: string; + icon: IconComponent; + title: string; +}; + +export type NavTargetOverrideConfig = { + path?: string; + icon?: IconComponent; + title?: string; +}; From 97877ca296a08ac0c7009e6be64a1052dd584260 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 May 2020 12:25:05 +0200 Subject: [PATCH 32/73] packages/core: new addRoute to router hook that adds a nav-target route --- packages/core/src/api/app/AppBuilder.tsx | 13 +++++++++++++ packages/core/src/api/plugin/Plugin.tsx | 15 +++++++++++++++ packages/core/src/api/plugin/types.ts | 9 +++++++++ 3 files changed, 37 insertions(+) diff --git a/packages/core/src/api/app/AppBuilder.tsx b/packages/core/src/api/app/AppBuilder.tsx index f65482b996..57c4eccf16 100644 --- a/packages/core/src/api/app/AppBuilder.tsx +++ b/packages/core/src/api/app/AppBuilder.tsx @@ -84,6 +84,19 @@ export default class AppBuilder { ); break; } + case 'nav-target-component': { + const { target, component, options = {} } = output; + const { exact = true } = options; + routes.push( + , + ); + break; + } case 'redirect-route': { const { path, target, options = {} } = output; const { exact = true } = options; diff --git a/packages/core/src/api/plugin/Plugin.tsx b/packages/core/src/api/plugin/Plugin.tsx index 3d067c66ad..d4f4aed2a6 100644 --- a/packages/core/src/api/plugin/Plugin.tsx +++ b/packages/core/src/api/plugin/Plugin.tsx @@ -22,6 +22,7 @@ import { FeatureFlagName, } from './types'; import { validateBrowserCompat, validateFlagName } from '../app/FeatureFlags'; +import { NavTarget } from '../navTargets'; export type PluginConfig = { id: string; @@ -34,6 +35,12 @@ export type PluginHooks = { }; export type RouterHooks = { + addRoute( + target: NavTarget, + Component: ComponentType, + options?: RouteOptions, + ): void; + registerRoute( path: RoutePath, Component: ComponentType, @@ -75,6 +82,14 @@ export default class Plugin { this.config.register({ router: { + addRoute(target, component, options) { + outputs.push({ + type: 'nav-target-component', + target, + component, + options, + }); + }, registerRoute(path, component, options) { outputs.push({ type: 'route', path, component, options }); }, diff --git a/packages/core/src/api/plugin/types.ts b/packages/core/src/api/plugin/types.ts index c4ac426e5c..2c471f5aca 100644 --- a/packages/core/src/api/plugin/types.ts +++ b/packages/core/src/api/plugin/types.ts @@ -15,6 +15,7 @@ */ import { ComponentType } from 'react'; +import { NavTarget } from '../navTargets'; export type RouteOptions = { // Whether the route path must match exactly, defaults to true. @@ -30,6 +31,13 @@ export type RouteOutput = { options?: RouteOptions; }; +export type RouteTargetOutput = { + type: 'nav-target-component'; + target: NavTarget; + component: ComponentType<{}>; + options?: RouteOptions; +}; + export type RedirectRouteOutput = { type: 'redirect-route'; path: RoutePath; @@ -46,5 +54,6 @@ export type FeatureFlagOutput = { export type PluginOutput = | RouteOutput + | RouteTargetOutput | RedirectRouteOutput | FeatureFlagOutput; From 14c551c616954fa8df0b3ad13ca45c2f482aeb36 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 May 2020 12:25:36 +0200 Subject: [PATCH 33/73] packages/dev-utils: add nav targets to the sidebar --- packages/dev-utils/src/devApp/render.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 6649f1055d..e68dac4021 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -127,6 +127,18 @@ class DevAppBuilder { ); break; } + case 'nav-target-component': { + const { target } = output; + sidebarItems.push( + , + ); + break; + } default: break; } From aa7eb96829126feff1839355d4b0cd3d56ec3f72 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 May 2020 12:26:32 +0200 Subject: [PATCH 34/73] plugins/graphiql: use nav-target route --- plugins/graphiql/src/index.ts | 1 + plugins/graphiql/src/navTargets.tsx | 92 +++++++++++++++++++++++++++++ plugins/graphiql/src/plugin.ts | 3 +- 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 plugins/graphiql/src/navTargets.tsx diff --git a/plugins/graphiql/src/index.ts b/plugins/graphiql/src/index.ts index a1f5587fe1..5590a0d60f 100644 --- a/plugins/graphiql/src/index.ts +++ b/plugins/graphiql/src/index.ts @@ -16,3 +16,4 @@ export { plugin } from './plugin'; export * from './lib/api'; +export * from './navTargets'; diff --git a/plugins/graphiql/src/navTargets.tsx b/plugins/graphiql/src/navTargets.tsx new file mode 100644 index 0000000000..febb6be59a --- /dev/null +++ b/plugins/graphiql/src/navTargets.tsx @@ -0,0 +1,92 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { FC } from 'react'; +import { createNavTarget } from '@backstage/core'; +import { SvgIcon, SvgIconProps } from '@material-ui/core'; + +const GraphiQLIcon: FC = props => ( + + + + + + + + + + + + + + + + + + + +); + +export const navTargetGraphiQL = createNavTarget({ + icon: GraphiQLIcon, + path: '/graphiql', + title: 'GraphiQL', +}); diff --git a/plugins/graphiql/src/plugin.ts b/plugins/graphiql/src/plugin.ts index 6436380c27..9c73324b43 100644 --- a/plugins/graphiql/src/plugin.ts +++ b/plugins/graphiql/src/plugin.ts @@ -16,10 +16,11 @@ import { createPlugin } from '@backstage/core'; import { GraphiQLPage } from './components'; +import { navTargetGraphiQL } from './navTargets'; export const plugin = createPlugin({ id: 'graphiql', register({ router }) { - router.registerRoute('/graphiql', GraphiQLPage); + router.addRoute(navTargetGraphiQL, GraphiQLPage); }, }); From 6faf3709d649024dcde0cc6fb389991bed0882da Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 May 2020 12:41:25 +0200 Subject: [PATCH 35/73] packages/core: fix layout/Sidebar icon prop type --- packages/core/src/layout/Sidebar/Items.tsx | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/core/src/layout/Sidebar/Items.tsx b/packages/core/src/layout/Sidebar/Items.tsx index 87a3038a55..62f02ca58b 100644 --- a/packages/core/src/layout/Sidebar/Items.tsx +++ b/packages/core/src/layout/Sidebar/Items.tsx @@ -14,19 +14,13 @@ * limitations under the License. */ -import { - Link, - makeStyles, - styled, - SvgIcon, - Theme, - Typography, -} from '@material-ui/core'; +import { Link, makeStyles, styled, Theme, Typography } from '@material-ui/core'; import clsx from 'clsx'; import React, { FC, useContext } from 'react'; import { sidebarConfig, SidebarContext } from './config'; +import { IconComponent } from '../../icons'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { color: '#b5b5b5', display: 'flex', @@ -59,7 +53,7 @@ const useStyles = makeStyles(theme => ({ })); type SidebarItemProps = { - icon: typeof SvgIcon; + icon: IconComponent; text: string; to?: string; onClick?: () => void; From d86baf0ec53ebb104b6d5ed5500963e0ea408689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 13 May 2020 11:57:22 +0200 Subject: [PATCH 36/73] Move out the refresh logic into a separate class --- .../src/catalog/CatalogLogic.test.ts | 74 +++++++++++++++++++ .../src/catalog/CatalogLogic.ts | 60 +++++++++++++++ .../src/catalog/DatabaseCatalog.test.ts | 24 ++++-- .../src/catalog/DatabaseCatalog.ts | 60 ++++++--------- .../src/catalog/StaticCatalog.ts | 15 +++- plugins/catalog-backend/src/catalog/types.ts | 3 +- .../src/descriptors/component.ts | 2 +- .../src/ingestion/fileLocation.ts | 2 +- .../catalog-backend/src/ingestion/index.ts | 1 + .../catalog-backend/src/ingestion/types.ts | 19 +++++ 10 files changed, 206 insertions(+), 54 deletions(-) create mode 100644 plugins/catalog-backend/src/catalog/CatalogLogic.test.ts create mode 100644 plugins/catalog-backend/src/catalog/CatalogLogic.ts create mode 100644 plugins/catalog-backend/src/ingestion/types.ts diff --git a/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts b/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts new file mode 100644 index 0000000000..f8e455e1de --- /dev/null +++ b/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { PassThrough } from 'stream'; +import winston from 'winston'; +import { CatalogLogic } from './CatalogLogic'; +import { Catalog } from './types'; + +describe('CatalogLogic', () => { + const logger = winston.createLogger({ + transports: [new winston.transports.Stream({ stream: new PassThrough() })], + }); + + describe('refreshLocations', () => { + it('works with no locations added', async () => { + const catalog = ({ + addOrUpdateComponent: jest.fn(), + locations: jest.fn(() => []), + } as unknown) as Catalog; + const locationReader = jest.fn(); + + await expect( + CatalogLogic.refreshLocations(catalog, locationReader, logger), + ).resolves.toBeUndefined(); + expect(locationReader).not.toHaveBeenCalled(); + }); + + it('can update a single location', async () => { + const catalog = ({ + addOrUpdateComponent: jest.fn(), + locations: jest.fn(() => [ + { + id: '123', + type: 'some', + target: 'thing', + }, + ]), + } as unknown) as Catalog; + + const locationReader = jest.fn().mockResolvedValue([ + { + name: 'c1', + }, + ]); + + await expect( + CatalogLogic.refreshLocations(catalog, locationReader, logger), + ).resolves.toBeUndefined(); + expect(locationReader).toHaveBeenCalledTimes(1); + expect(locationReader).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ type: 'some' }), + ); + expect(catalog.addOrUpdateComponent).toHaveBeenCalledTimes(1); + expect(catalog.addOrUpdateComponent).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ name: 'c1' }), + ); + }); + }); +}); diff --git a/plugins/catalog-backend/src/catalog/CatalogLogic.ts b/plugins/catalog-backend/src/catalog/CatalogLogic.ts new file mode 100644 index 0000000000..d8c4804074 --- /dev/null +++ b/plugins/catalog-backend/src/catalog/CatalogLogic.ts @@ -0,0 +1,60 @@ +/* + * 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 { LocationReader } from '../ingestion/types'; +import { Catalog } from './types'; + +export class CatalogLogic { + public static startRefreshLoop( + catalog: Catalog, + reader: LocationReader, + logger: Logger, + ): () => void { + let cancelled = false; + + const startRefresh = async () => { + while (!cancelled) { + await CatalogLogic.refreshLocations(catalog, reader, logger); + await new Promise((resolve) => setTimeout(resolve, 10000)); + } + }; + startRefresh(); + + return () => { + cancelled = true; + }; + } + + public static async refreshLocations( + catalog: Catalog, + reader: LocationReader, + logger: Logger, + ): Promise { + const locations = await catalog.locations(); + for (const location of locations) { + try { + logger.debug(`Attempting refresh of location: ${location.id}`); + const components = await reader(location); + for (const component of components) { + await catalog.addOrUpdateComponent(component); + } + } catch (e) { + logger.debug(`Failed to update location "${location.id}", ${e}`); + } + } + } +} diff --git a/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts b/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts index 038314e653..80a0377445 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts @@ -38,15 +38,23 @@ describe('DatabaseCatalog', () => { }); }); - it('instantiates', () => { + it('manages locations', async () => { const catalog = new DatabaseCatalog(database, logger); - expect(catalog).toBeDefined(); - }); + const input = { type: 'a', target: 'b' }; + const output = { id: expect.anything(), type: 'a', target: 'b' }; - describe(`refreshLocations`, () => { - it('works with no locations added', async () => { - const catalog = new DatabaseCatalog(database, logger); - await expect(catalog.refreshLocations()).resolves.toBeUndefined(); - }); + await catalog.addLocation(input); + + const locations = await catalog.locations(); + expect(locations).toEqual([output]); + const location = await catalog.location(locations[0].id); + expect(location).toEqual(output); + + await catalog.removeLocation(locations[0].id); + + await expect(catalog.locations()).resolves.toEqual([]); + await expect(catalog.location(locations[0].id)).rejects.toThrowError( + /Found no location/, + ); }); }); diff --git a/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts index 51cccf2286..39a0719201 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts @@ -20,6 +20,7 @@ import path from 'path'; import { v4 as uuidv4 } from 'uuid'; import { Logger } from 'winston'; import { readLocation } from '../ingestion'; +import { CatalogLogic } from './CatalogLogic'; import { AddLocationRequest, Catalog, Component, Location } from './types'; export class DatabaseCatalog implements Catalog { @@ -33,14 +34,7 @@ export class DatabaseCatalog implements Catalog { }); const databaseCatalog = new DatabaseCatalog(database, logger); - - const startRefresh = async () => { - for (;;) { - await databaseCatalog.refreshLocations(); - await new Promise(r => setTimeout(r, 10000)); - } - }; - startRefresh(); + CatalogLogic.startRefreshLoop(databaseCatalog, readLocation, logger); return databaseCatalog; } @@ -50,13 +44,28 @@ export class DatabaseCatalog implements Catalog { private readonly logger: Logger, ) {} + async addOrUpdateComponent(component: Component): Promise { + await this.database.transaction(async (tx) => { + await tx('components') + .insert(component) + .catch(() => + tx('components').where({ name: component.name }).update(component), + ); + }); + return component; + } + async components(): Promise { - throw new Error('Not supported'); + return await this.database('components').select(); } // eslint-disable-next-line @typescript-eslint/no-unused-vars async component(name: string): Promise { - throw new Error('Not supported'); + const items = await this.database('components').where({ name }).select(); + if (!items.length) { + throw new NotFoundError(`Found no component with name ${name}`); + } + return items[0]; } async addLocation(location: AddLocationRequest): Promise { @@ -67,42 +76,15 @@ export class DatabaseCatalog implements Catalog { } async removeLocation(id: string): Promise { - const result = await this.database('locations') - .where({ id }) - .del(); + const result = await this.database('locations').where({ id }).del(); if (!result) { throw new NotFoundError(`Found no location with ID ${id}`); } } - async refreshLocations(): Promise { - const locations = await this.locations(); - for (const location of locations) { - try { - this.logger.debug(`Attempting refresh of location: ${location.id}`); - const components = await readLocation(location); - for (const component of components) { - await this.database.transaction(async tx => { - await tx('components') - .insert(component) - .catch(() => - tx('components') - .where({ name: component.name }) - .update(component), - ); - }); - } - } catch (e) { - this.logger.debug(`Failed to update location "${location.id}", ${e}`); - } - } - } - async location(id: string): Promise { - const items = await this.database('locations') - .where({ id }) - .select(); + const items = await this.database('locations').where({ id }).select(); if (!items.length) { throw new NotFoundError(`Found no location with ID ${id}`); } diff --git a/plugins/catalog-backend/src/catalog/StaticCatalog.ts b/plugins/catalog-backend/src/catalog/StaticCatalog.ts index 43229929b8..b98798f644 100644 --- a/plugins/catalog-backend/src/catalog/StaticCatalog.ts +++ b/plugins/catalog-backend/src/catalog/StaticCatalog.ts @@ -16,7 +16,7 @@ import { NotFoundError } from '@backstage/backend-common'; import { v4 as uuidv4 } from 'uuid'; -import { AddLocationRequest, Component, Catalog, Location } from './types'; +import { AddLocationRequest, Catalog, Component, Location } from './types'; export class StaticCatalog implements Catalog { private _components: Component[]; @@ -27,12 +27,19 @@ export class StaticCatalog implements Catalog { this._locations = locations; } + async addOrUpdateComponent(component: Component): Promise { + this._components = this._components + .filter((c) => c.name !== component.name) + .concat([component]); + return component; + } + async components(): Promise { return this._components.slice(); } async component(name: string): Promise { - const item = this._components.find(i => i.name === name); + const item = this._components.find((i) => i.name === name); if (!item) { throw new NotFoundError(`Found no component with name ${name}`); } @@ -46,11 +53,11 @@ export class StaticCatalog implements Catalog { } async removeLocation(id: string): Promise { - this._locations = this._locations.filter(l => l.id !== id); + this._locations = this._locations.filter((l) => l.id !== id); } async location(id: string): Promise { - const location = this._locations.find(l => l.id === id); + const location = this._locations.find((l) => l.id === id); if (!location) { throw new NotFoundError(`Found no location with ID ${id}`); } diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index e91c5f8267..ed33f35b3d 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -39,10 +39,11 @@ export const addLocationRequestShape: yup.Schema = yup .noUnknown(); export type Catalog = { + addOrUpdateComponent(component: Component): Promise; components(): Promise; component(id: string): Promise; addLocation(location: AddLocationRequest): Promise; removeLocation(id: string): Promise; - location(id: string): Promise; locations(): Promise; + location(id: string): Promise; }; diff --git a/plugins/catalog-backend/src/descriptors/component.ts b/plugins/catalog-backend/src/descriptors/component.ts index cb0c30aa61..0ecaf77c66 100644 --- a/plugins/catalog-backend/src/descriptors/component.ts +++ b/plugins/catalog-backend/src/descriptors/component.ts @@ -15,8 +15,8 @@ */ import * as yup from 'yup'; -import { DescriptorEnvelope } from './envelope'; import { Component } from '../catalog/types'; +import { DescriptorEnvelope } from './envelope'; export type ComponentDescriptor = { metadata: { diff --git a/plugins/catalog-backend/src/ingestion/fileLocation.ts b/plugins/catalog-backend/src/ingestion/fileLocation.ts index 524721035e..3c1b985356 100644 --- a/plugins/catalog-backend/src/ingestion/fileLocation.ts +++ b/plugins/catalog-backend/src/ingestion/fileLocation.ts @@ -15,8 +15,8 @@ */ import fs from 'fs-extra'; -import { parseDescriptor } from '../descriptors'; import { Component } from '../catalog/types'; +import { parseDescriptor } from '../descriptors'; export async function readFileLocation(target: string): Promise { let rawYaml; diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index 775fe29f84..e96be6822a 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -15,4 +15,5 @@ */ export * from './fileLocation'; +export * from './types'; export * from './util'; diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts new file mode 100644 index 0000000000..5808c7e592 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/types.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. + */ + +import { Component, Location } from '../catalog'; + +export type LocationReader = (location: Location) => Promise; From 2bde9d385458f6eb106bc35b177e9c2fc70844a2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 13 May 2020 16:28:41 +0200 Subject: [PATCH 37/73] v0.1.1-alpha.5 --- lerna.json | 2 +- packages/app/package.json | 26 ++++++++++++------------- packages/backend-common/package.json | 4 ++-- packages/backend/package.json | 10 +++++----- packages/cli/package.json | 2 +- packages/core/package.json | 10 +++++----- packages/dev-utils/package.json | 10 +++++----- packages/storybook/package.json | 4 ++-- packages/test-utils-core/package.json | 2 +- packages/test-utils/package.json | 12 ++++++------ packages/theme/package.json | 4 ++-- plugins/catalog-backend/package.json | 6 +++--- plugins/catalog/package.json | 12 ++++++------ plugins/explore/package.json | 14 ++++++------- plugins/graphiql/package.json | 12 ++++++------ plugins/home-page/package.json | 14 ++++++------- plugins/lighthouse/package.json | 14 ++++++------- plugins/register-component/package.json | 10 +++++----- plugins/scaffolder-backend/package.json | 14 ++++++------- plugins/scaffolder/package.json | 12 ++++++------ plugins/tech-radar/package.json | 14 ++++++------- plugins/welcome/package.json | 12 ++++++------ 22 files changed, 110 insertions(+), 110 deletions(-) diff --git a/lerna.json b/lerna.json index 1d77db92fb..39a7a4aae6 100644 --- a/lerna.json +++ b/lerna.json @@ -5,5 +5,5 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.1.1-alpha.4" + "version": "0.1.1-alpha.5" } diff --git a/packages/app/package.json b/packages/app/package.json index 45d089c3db..bce1f8c01c 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,19 +1,19 @@ { "name": "example-app", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "private": true, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.4", - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/plugin-explore": "^0.1.1-alpha.4", - "@backstage/plugin-home-page": "^0.1.1-alpha.4", - "@backstage/plugin-catalog": "^0.1.1-alpha.4", - "@backstage/plugin-lighthouse": "^0.1.1-alpha.4", - "@backstage/plugin-register-component": "^0.1.1-alpha.4", - "@backstage/plugin-scaffolder": "^0.1.1-alpha.4", - "@backstage/plugin-tech-radar": "^0.1.1-alpha.4", - "@backstage/plugin-welcome": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/plugin-catalog": "^0.1.1-alpha.5", + "@backstage/plugin-explore": "^0.1.1-alpha.5", + "@backstage/plugin-home-page": "^0.1.1-alpha.5", + "@backstage/plugin-lighthouse": "^0.1.1-alpha.5", + "@backstage/plugin-register-component": "^0.1.1-alpha.5", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.5", + "@backstage/plugin-tech-radar": "^0.1.1-alpha.5", + "@backstage/plugin-welcome": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -26,11 +26,11 @@ }, "devDependencies": { "@testing-library/cypress": "^6.0.0", - "@types/jquery": "^3.3.34", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/jest": "^25.2.1", + "@types/jquery": "^3.3.34", "@types/node": "^12.0.0", "@types/react-router-dom": "^5.1.3", "@types/zen-observable": "^0.8.0", diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 7a8886adc3..55f66c66a1 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist", "private": false, "publishConfig": { @@ -29,7 +29,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", "@types/express": "^4.17.6", "@types/http-errors": "^1.6.3", "@types/morgan": "^1.9.0", diff --git a/packages/backend/package.json b/packages/backend/package.json index 23cd0f05ba..3a24067da4 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist", "private": true, "license": "Apache-2.0", @@ -16,9 +16,9 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "0.1.1-alpha.4", - "@backstage/plugin-catalog-backend": "0.1.1-alpha.4", - "@backstage/plugin-scaffolder-backend": "0.1.1-alpha.4", + "@backstage/backend-common": "^0.1.1-alpha.5", + "@backstage/plugin-catalog-backend": "^0.1.1-alpha.5", + "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.5", "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.17.1", @@ -28,7 +28,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", "@types/compression": "^1.7.0", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", diff --git a/packages/cli/package.json b/packages/cli/package.json index 15f711f1b9..125ae46b88 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "private": false, "publishConfig": { "access": "public" diff --git a/packages/core/package.json b/packages/core/package.json index 475496e633..df69a9353b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "private": false, "publishConfig": { "access": "public" @@ -25,7 +25,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "classnames": "^2.2.6", @@ -44,9 +44,9 @@ "react-syntax-highlighter": "^12.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", - "@backstage/test-utils": "0.1.1-alpha.4", - "@backstage/test-utils-core": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/test-utils": "^0.1.1-alpha.5", + "@backstage/test-utils-core": "^0.1.1-alpha.5", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 1dcf605a36..605bd8b6ef 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "private": false, "publishConfig": { "access": "public" @@ -25,10 +25,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.4", - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/test-utils": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/test-utils": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^4.2.4", diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 81c2346d14..64be72bdcf 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "description": "Storybook build for core package", "private": true, "scripts": { @@ -14,7 +14,7 @@ ] }, "dependencies": { - "@backstage/theme": "0.1.1-alpha.4" + "@backstage/theme": "^0.1.1-alpha.5" }, "devDependencies": { "@storybook/addon-actions": "^5.3.17", diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index af73688612..ce13becdb2 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils-core", "description": "Utilities to test Backstage core", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "private": false, "publishConfig": { "access": "public" diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index e70fa7c8d4..512af5020f 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "private": false, "publishConfig": { "access": "public" @@ -25,16 +25,16 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.4", - "@backstage/test-utils-core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/test-utils-core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", + "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "react": "^16.12.0", "@types/jest": "^25.2.1", "@types/node": "^12.0.0", - "@testing-library/jest-dom": "^4.2.4", + "react": "^16.12.0", "react-dom": "^16.12.0", "react-router": "^5.2.0", "react-router-dom": "^5.2.0" diff --git a/packages/theme/package.json b/packages/theme/package.json index 18b4dd7fa2..38392959e8 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/theme", "description": "material-ui theme for use with Backstage.", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "private": false, "publishConfig": { "access": "public" @@ -27,7 +27,7 @@ "@material-ui/core": "^4.9.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4" + "@backstage/cli": "^0.1.1-alpha.5" }, "files": [ "dist" diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 1a819c28b3..c0c8f0b396 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist", "license": "Apache-2.0", "private": true, @@ -12,7 +12,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "0.1.1-alpha.4", + "@backstage/backend-common": "^0.1.1-alpha.5", "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.17.1", @@ -27,7 +27,7 @@ "yup": "^0.28.5" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", "@types/uuid": "^7.0.3", "@types/yup": "^0.28.2", "jest-fetch-mock": "^3.0.3", diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 2ecade33d5..3c46b5034f 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", "types": "dist/index.d.ts", "license": "Apache-2.0", @@ -14,8 +14,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -24,15 +24,15 @@ "react-use": "^13.24.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3", - "@backstage/dev-utils": "^0.1.1-alpha.4" + "jest-fetch-mock": "^3.0.3" }, "files": [ "dist" diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 93a560e830..c330068940 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", "types": "dist/index.d.ts", "license": "Apache-2.0", @@ -14,8 +14,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -25,16 +25,16 @@ "react-use": "^13.24.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", - "@backstage/test-utils": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", + "@backstage/test-utils": "^0.1.1-alpha.5", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3", - "@backstage/dev-utils": "^0.1.1-alpha.4" + "jest-fetch-mock": "^3.0.3" }, "files": [ "dist" diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 56872de2db..a81ce53e5a 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "private": false, "publishConfig": { "access": "public" @@ -27,8 +27,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -39,9 +39,9 @@ "react-use": "^13.24.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", - "@backstage/dev-utils": "^0.1.1-alpha.4", - "@backstage/test-utils": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", + "@backstage/test-utils": "^0.1.1-alpha.5", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json index 134e8e387b..9a30bb394f 100644 --- a/plugins/home-page/package.json +++ b/plugins/home-page/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-page", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", "types": "dist/index.d.ts", "license": "Apache-2.0", @@ -14,8 +14,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -24,16 +24,16 @@ "react-use": "^13.24.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", - "@backstage/dev-utils": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "react-router-dom": "^5.2.0", - "jest-fetch-mock": "^3.0.3" + "jest-fetch-mock": "^3.0.3", + "react-router-dom": "^5.2.0" }, "files": [ "dist" diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 49d97390a0..200897b7c3 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", "types": "dist/index.d.ts", "license": "Apache-2.0", @@ -14,8 +14,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -26,16 +26,16 @@ "react-use": "^13.24.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", - "@backstage/test-utils": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", + "@backstage/test-utils": "^0.1.1-alpha.5", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3", - "@backstage/dev-utils": "^0.1.1-alpha.4" + "jest-fetch-mock": "^3.0.3" }, "files": [ "dist" diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index a851be6ba4..4fc5af10da 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", "types": "dist/index.d.ts", "license": "Apache-2.0", @@ -14,8 +14,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -25,8 +25,8 @@ "react-use": "^13.24.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", - "@backstage/dev-utils": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index a1a80736de..f1c97bbce8 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist", "license": "Apache-2.0", "private": true, @@ -11,20 +11,20 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "0.1.1-alpha.4", - "dockerode": "^3.2.0", - "express": "^4.17.1", - "fs-extra": "^9.0.0", - "globby": "^11.0.0", + "@backstage/backend-common": "^0.1.1-alpha.5", "compression": "^1.7.4", "cors": "^2.8.5", + "dockerode": "^3.2.0", + "express": "^4.17.1", "express-promise-router": "^3.0.3", + "fs-extra": "^9.0.0", + "globby": "^11.0.0", "helmet": "^3.22.0", "morgan": "^1.10.0", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", "@types/fs-extra": "^8.1.0", "@types/supertest": "^2.0.8", "supertest": "^4.0.2" diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 0278aa5970..ba686fc016 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", "types": "dist/index.d.ts", "license": "Apache-2.0", @@ -14,8 +14,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -24,15 +24,15 @@ "react-use": "^13.24.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3", - "@backstage/dev-utils": "^0.1.1-alpha.4" + "jest-fetch-mock": "^3.0.3" }, "files": [ "dist" diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index d45c14a692..cbddd0c7db 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-radar", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", "types": "dist/index.d.ts", "license": "Apache-2.0", @@ -14,9 +14,9 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/test-utils-core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/test-utils-core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -28,7 +28,8 @@ "react-use": "^13.24.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", @@ -37,8 +38,7 @@ "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3", - "@backstage/dev-utils": "^0.1.1-alpha.4" + "jest-fetch-mock": "^3.0.3" }, "files": [ "dist" diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index d1d49f4c08..ea1c579af3 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-welcome", - "version": "0.1.1-alpha.4", + "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", "types": "dist/index.d.ts", "private": true, @@ -14,8 +14,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -25,15 +25,15 @@ "react-use": "^13.24.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", - "jest-fetch-mock": "^3.0.3", - "@backstage/dev-utils": "^0.1.1-alpha.4" + "jest-fetch-mock": "^3.0.3" }, "files": [ "dist" From 7cb381b664858995f3da0c82ce0db77d5d3985e6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 13 May 2020 15:07:22 +0000 Subject: [PATCH 38/73] build(deps): bump replace-in-file from 5.0.2 to 6.0.0 Bumps [replace-in-file](https://github.com/adamreisnz/replace-in-file) from 5.0.2 to 6.0.0. - [Release notes](https://github.com/adamreisnz/replace-in-file/releases) - [Changelog](https://github.com/adamreisnz/replace-in-file/blob/master/CHANGELOG.md) - [Commits](https://github.com/adamreisnz/replace-in-file/commits) Signed-off-by: dependabot-preview[bot] --- packages/cli/package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 125ae46b88..3f08feb738 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -55,7 +55,7 @@ "react-dev-utils": "^10.2.0", "react-scripts": "^3.4.1", "recursive-readdir": "^2.2.2", - "replace-in-file": "^5.0.2", + "replace-in-file": "^6.0.0", "rollup": "^2.3.2", "rollup-plugin-image-files": "^1.4.2", "rollup-plugin-peer-deps-external": "^2.2.2", diff --git a/yarn.lock b/yarn.lock index 93303c3801..3f9f60881c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18491,14 +18491,14 @@ replace-ext@1.0.0: resolved "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= -replace-in-file@^5.0.2: - version "5.0.2" - resolved "https://registry.npmjs.org/replace-in-file/-/replace-in-file-5.0.2.tgz#bd26203b66dfb5b8112ae36a2d2cf928ea4cfe12" - integrity sha512-1Vc7Sbr/rTuHgU1PZuBb7tGsFx3D4NKdhV4BpEF2MuN/6+SoXcFtx+dZ1Zz+5Dq4k5x9js87Y+gXQYPTQ9ppkA== +replace-in-file@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.0.0.tgz#a583be911c4af3ecbf709ca97c48f3f9e7f2b626" + integrity sha512-vMmJekpRgju0GA0UvRxauDbQ645wGXxasVZfw5l5HKk58OlAI1tMmXNcV+YAUKrS/XFdBPla5qgvjI5Tqvshvg== dependencies: - chalk "^3.0.0" + chalk "^4.0.0" glob "^7.1.6" - yargs "^15.0.2" + yargs "^15.3.1" request-progress@3.0.0: version "3.0.0" @@ -22254,7 +22254,7 @@ yargs@^14.2.2: y18n "^4.0.0" yargs-parser "^15.0.1" -yargs@^15.0.0, yargs@^15.0.1, yargs@^15.0.2: +yargs@^15.0.0, yargs@^15.0.1, yargs@^15.3.1: version "15.3.1" resolved "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== From 3f00f82e28de4785e4a4b82c06f32dc38941291b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 13 May 2020 18:47:53 +0200 Subject: [PATCH 39/73] packages/core: use cleaner tree of index files in api + remove default exports --- .../core/src/api/apis/ApiProvider.test.tsx | 6 +++--- packages/core/src/api/apis/ApiProvider.tsx | 8 +++----- packages/core/src/api/apis/ApiRef.test.ts | 2 +- packages/core/src/api/apis/ApiRef.ts | 6 +++++- .../core/src/api/apis/ApiRegistry.test.ts | 4 ++-- packages/core/src/api/apis/ApiRegistry.ts | 4 ++-- .../core/src/api/apis/ApiTestRegistry.test.ts | 4 ++-- packages/core/src/api/apis/ApiTestRegistry.ts | 4 ++-- .../core/src/api/apis/definitions/alert.ts | 2 +- .../core/src/api/apis/definitions/error.ts | 2 +- .../src/api/apis/definitions/featureFlags.ts | 2 +- packages/core/src/api/apis/index.ts | 8 ++++---- packages/core/src/api/apis/types.ts | 2 +- packages/core/src/api/app/AppBuilder.tsx | 8 ++++++-- .../core/src/api/{api.ts => app/index.ts} | 19 ++++--------------- packages/core/src/api/index.ts | 5 ++--- packages/core/src/api/plugin/Plugin.tsx | 10 ++++++---- packages/core/src/api/plugin/index.ts | 18 ++++++++++++++++++ packages/core/src/api/plugin/types.ts | 5 +++++ 19 files changed, 69 insertions(+), 50 deletions(-) rename packages/core/src/api/{api.ts => app/index.ts} (58%) create mode 100644 packages/core/src/api/plugin/index.ts diff --git a/packages/core/src/api/apis/ApiProvider.test.tsx b/packages/core/src/api/apis/ApiProvider.test.tsx index f572907c2f..e79e83a1ed 100644 --- a/packages/core/src/api/apis/ApiProvider.test.tsx +++ b/packages/core/src/api/apis/ApiProvider.test.tsx @@ -15,9 +15,9 @@ */ import React from 'react'; -import ApiProvider, { useApi, withApis } from './ApiProvider'; -import ApiRef from './ApiRef'; -import ApiRegistry from './ApiRegistry'; +import { ApiProvider, useApi, withApis } from './ApiProvider'; +import { ApiRef } from './ApiRef'; +import { ApiRegistry } from './ApiRegistry'; import { render } from '@testing-library/react'; import { withLogCollector } from '@backstage/test-utils-core'; diff --git a/packages/core/src/api/apis/ApiProvider.tsx b/packages/core/src/api/apis/ApiProvider.tsx index db87cc1610..46f89cfa0d 100644 --- a/packages/core/src/api/apis/ApiProvider.tsx +++ b/packages/core/src/api/apis/ApiProvider.tsx @@ -16,7 +16,7 @@ import React, { FC, createContext, useContext, ReactNode } from 'react'; import PropTypes from 'prop-types'; -import ApiRef from './ApiRef'; +import { ApiRef } from './ApiRef'; import { ApiHolder, TypesToApiRefs } from './types'; type Props = { @@ -26,7 +26,7 @@ type Props = { const Context = createContext(undefined); -const ApiProvider: FC = ({ apis, children }) => { +export const ApiProvider: FC = ({ apis, children }) => { return ; }; @@ -53,7 +53,7 @@ export function withApis(apis: TypesToApiRefs) { return function withApisWrapper

( WrappedComponent: React.ComponentType

, ) { - const Hoc: FC> = props => { + const Hoc: FC> = (props) => { const apiHolder = useContext(Context); if (!apiHolder) { @@ -84,5 +84,3 @@ export function withApis(apis: TypesToApiRefs) { return Hoc; }; } - -export default ApiProvider; diff --git a/packages/core/src/api/apis/ApiRef.test.ts b/packages/core/src/api/apis/ApiRef.test.ts index b592e327b0..93ca639b46 100644 --- a/packages/core/src/api/apis/ApiRef.test.ts +++ b/packages/core/src/api/apis/ApiRef.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import ApiRef from './ApiRef'; +import { ApiRef } from './ApiRef'; describe('ApiRef', () => { it('should be created', () => { diff --git a/packages/core/src/api/apis/ApiRef.ts b/packages/core/src/api/apis/ApiRef.ts index b3f4d48428..c346f89685 100644 --- a/packages/core/src/api/apis/ApiRef.ts +++ b/packages/core/src/api/apis/ApiRef.ts @@ -19,7 +19,7 @@ export type ApiRefConfig = { description: string; }; -export default class ApiRef { +export class ApiRef { constructor(private readonly config: ApiRefConfig) { if (!config.id.match(/^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$/)) { throw new Error( @@ -45,3 +45,7 @@ export default class ApiRef { return `apiRef{${this.config.id}}`; } } + +export function createApiRef(config: ApiRefConfig) { + return new ApiRef(config); +} diff --git a/packages/core/src/api/apis/ApiRegistry.test.ts b/packages/core/src/api/apis/ApiRegistry.test.ts index 6eb78892b2..a4043bbe1e 100644 --- a/packages/core/src/api/apis/ApiRegistry.test.ts +++ b/packages/core/src/api/apis/ApiRegistry.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import ApiRegistry from './ApiRegistry'; -import ApiRef from './ApiRef'; +import { ApiRegistry } from './ApiRegistry'; +import { ApiRef } from './ApiRef'; describe('ApiRegistry', () => { const x1Ref = new ApiRef({ id: 'x', description: '' }); diff --git a/packages/core/src/api/apis/ApiRegistry.ts b/packages/core/src/api/apis/ApiRegistry.ts index 4149365806..0a9d14a4e7 100644 --- a/packages/core/src/api/apis/ApiRegistry.ts +++ b/packages/core/src/api/apis/ApiRegistry.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import ApiRef from './ApiRef'; +import { ApiRef } from './ApiRef'; import { ApiHolder } from './types'; type ApiImpl = readonly [ApiRef, T]; @@ -33,7 +33,7 @@ class ApiRegistryBuilder { } } -export default class ApiRegistry implements ApiHolder { +export class ApiRegistry implements ApiHolder { static builder() { return new ApiRegistryBuilder(); } diff --git a/packages/core/src/api/apis/ApiTestRegistry.test.ts b/packages/core/src/api/apis/ApiTestRegistry.test.ts index 6974daa3a7..6ae7a83d7e 100644 --- a/packages/core/src/api/apis/ApiTestRegistry.test.ts +++ b/packages/core/src/api/apis/ApiTestRegistry.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import ApiTestRegistry from './ApiTestRegistry'; -import ApiRef from './ApiRef'; +import { ApiTestRegistry } from './ApiTestRegistry'; +import { ApiRef } from './ApiRef'; describe('ApiTestRegistry', () => { const aRef = new ApiRef({ id: 'a', description: '' }); diff --git a/packages/core/src/api/apis/ApiTestRegistry.ts b/packages/core/src/api/apis/ApiTestRegistry.ts index 2af2058fbc..eacc4962ce 100644 --- a/packages/core/src/api/apis/ApiTestRegistry.ts +++ b/packages/core/src/api/apis/ApiTestRegistry.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import ApiRef from './ApiRef'; +import { ApiRef } from './ApiRef'; import { TypesToApiRefs, AnyApiRef, ApiHolder, ApiFactory } from './types'; -export default class ApiTestRegistry implements ApiHolder { +export class ApiTestRegistry implements ApiHolder { private readonly apis = new Map(); private factories = new Map< AnyApiRef, diff --git a/packages/core/src/api/apis/definitions/alert.ts b/packages/core/src/api/apis/definitions/alert.ts index 133255c719..e5d7001b56 100644 --- a/packages/core/src/api/apis/definitions/alert.ts +++ b/packages/core/src/api/apis/definitions/alert.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import ApiRef from '../ApiRef'; +import { ApiRef } from '../ApiRef'; export type AlertMessage = { message: string; diff --git a/packages/core/src/api/apis/definitions/error.ts b/packages/core/src/api/apis/definitions/error.ts index f2a8509558..7d9816426e 100644 --- a/packages/core/src/api/apis/definitions/error.ts +++ b/packages/core/src/api/apis/definitions/error.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import ApiRef from '../ApiRef'; +import { ApiRef } from '../ApiRef'; /** * Mirrors the javascript Error class, for the purpose of diff --git a/packages/core/src/api/apis/definitions/featureFlags.ts b/packages/core/src/api/apis/definitions/featureFlags.ts index 926fe091d5..8636c1f5c1 100644 --- a/packages/core/src/api/apis/definitions/featureFlags.ts +++ b/packages/core/src/api/apis/definitions/featureFlags.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import ApiRef from '../ApiRef'; +import { ApiRef } from '../ApiRef'; import { UserFlags, FeatureFlagsRegistry, diff --git a/packages/core/src/api/apis/index.ts b/packages/core/src/api/apis/index.ts index 9ab9a25e50..332636580c 100644 --- a/packages/core/src/api/apis/index.ts +++ b/packages/core/src/api/apis/index.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -export { default as ApiProvider, useApi } from './ApiProvider'; -export { default as ApiRegistry } from './ApiRegistry'; -export { default as ApiTestRegistry } from './ApiTestRegistry'; -export { default as ApiRef } from './ApiRef'; +export { ApiProvider, useApi } from './ApiProvider'; +export { ApiRegistry } from './ApiRegistry'; +export { ApiTestRegistry } from './ApiTestRegistry'; +export * from './ApiRef'; export * from './types'; export * from './helpers'; export * from './definitions'; diff --git a/packages/core/src/api/apis/types.ts b/packages/core/src/api/apis/types.ts index c44fd804a5..4ee9b39ca9 100644 --- a/packages/core/src/api/apis/types.ts +++ b/packages/core/src/api/apis/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import ApiRef from './ApiRef'; +import { ApiRef } from './ApiRef'; export type AnyApiRef = ApiRef; diff --git a/packages/core/src/api/app/AppBuilder.tsx b/packages/core/src/api/app/AppBuilder.tsx index 57c4eccf16..f9055a0bf7 100644 --- a/packages/core/src/api/app/AppBuilder.tsx +++ b/packages/core/src/api/app/AppBuilder.tsx @@ -18,7 +18,7 @@ import React, { ComponentType } from 'react'; import { Route, Switch, Redirect } from 'react-router-dom'; import { AppContextProvider } from './AppContext'; import { App } from './types'; -import BackstagePlugin from '../plugin/Plugin'; +import { BackstagePlugin } from '../plugin'; import { FeatureFlagsRegistryItem } from './FeatureFlags'; import { featureFlagsApiRef } from '../apis/definitions/featureFlags'; import ErrorPage from '../../layout/ErrorPage'; @@ -40,7 +40,7 @@ class AppImpl implements App { } } -export default class AppBuilder { +export class AppBuilder { private apis?: ApiHolder; private systemIcons = { ...defaultSystemIcons }; private readonly plugins = new Set(); @@ -145,3 +145,7 @@ export default class AppBuilder { return () => ; } } + +export function createApp() { + return new AppBuilder(); +} diff --git a/packages/core/src/api/api.ts b/packages/core/src/api/app/index.ts similarity index 58% rename from packages/core/src/api/api.ts rename to packages/core/src/api/app/index.ts index cafd0af126..156c9870b8 100644 --- a/packages/core/src/api/api.ts +++ b/packages/core/src/api/app/index.ts @@ -14,18 +14,7 @@ * limitations under the License. */ -import ApiRef, { ApiRefConfig } from './apis/ApiRef'; -import AppBuilder from './app/AppBuilder'; -import BackstagePlugin, { PluginConfig } from './plugin/Plugin'; - -export function createApp() { - return new AppBuilder(); -} - -export function createApiRef(config: ApiRefConfig) { - return new ApiRef(config); -} - -export function createPlugin(config: PluginConfig): BackstagePlugin { - return new BackstagePlugin(config); -} +export { createApp } from './AppBuilder'; +export { FeatureFlags } from './FeatureFlags'; +export { useApp } from './AppContext'; +export * from './types'; diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts index 8c8972c72c..0f111b26ba 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -export * from './api'; export * from './apis'; +export * from './app'; export * from './navTargets'; -export { FeatureFlags } from './app/FeatureFlags'; -export { useApp } from './app/AppContext'; +export * from './plugin'; diff --git a/packages/core/src/api/plugin/Plugin.tsx b/packages/core/src/api/plugin/Plugin.tsx index d4f4aed2a6..74aeaaa523 100644 --- a/packages/core/src/api/plugin/Plugin.tsx +++ b/packages/core/src/api/plugin/Plugin.tsx @@ -20,6 +20,7 @@ import { RoutePath, RouteOptions, FeatureFlagName, + BackstagePlugin, } from './types'; import { validateBrowserCompat, validateFlagName } from '../app/FeatureFlags'; import { NavTarget } from '../navTargets'; @@ -58,10 +59,7 @@ export type FeatureFlagsHooks = { register(name: FeatureFlagName): void; }; -export const registerSymbol = Symbol('plugin-register'); -export const outputSymbol = Symbol('plugin-output'); - -export default class Plugin { +export class PluginImpl { private storedOutput?: PluginOutput[]; constructor(private readonly config: PluginConfig) {} @@ -114,3 +112,7 @@ export default class Plugin { return `plugin{${this.config.id}}`; } } + +export function createPlugin(config: PluginConfig): BackstagePlugin { + return new PluginImpl(config); +} diff --git a/packages/core/src/api/plugin/index.ts b/packages/core/src/api/plugin/index.ts new file mode 100644 index 0000000000..79b0575755 --- /dev/null +++ b/packages/core/src/api/plugin/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { createPlugin } from './Plugin'; +export * from './types'; diff --git a/packages/core/src/api/plugin/types.ts b/packages/core/src/api/plugin/types.ts index 2c471f5aca..859813356a 100644 --- a/packages/core/src/api/plugin/types.ts +++ b/packages/core/src/api/plugin/types.ts @@ -57,3 +57,8 @@ export type PluginOutput = | RouteTargetOutput | RedirectRouteOutput | FeatureFlagOutput; + +export type BackstagePlugin = { + getId(): string; + output(): PluginOutput[]; +}; From 64bd3982c315952fc7c44449e72a557b2aa707f0 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 14 May 2020 08:37:04 +0200 Subject: [PATCH 40/73] build(deps-dev): bump nodemon from 2.0.2 to 2.0.3 (#853) Bumps [nodemon](https://github.com/remy/nodemon) from 2.0.2 to 2.0.3. - [Release notes](https://github.com/remy/nodemon/releases) - [Commits](https://github.com/remy/nodemon/compare/v2.0.2...v2.0.3) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- yarn.lock | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 231 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3f9f60881c..d8ec2fa50f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2886,6 +2886,11 @@ resolved "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#5405ee8e444ed212db44e79351f0c70a582aae25" integrity sha512-DetpxZw1fzPD5xUBrIAoplLChO2VB8DlL5Gg+I1IR9b2wPqYIca2WSUxL5g1vLeR4MsQq1NeWriXAVffV+U1Fw== +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + "@sinonjs/commons@^1.7.0": version "1.7.1" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz#da5fd19a5f71177a53778073978873964f49acf1" @@ -3726,6 +3731,13 @@ "@svgr/plugin-svgo" "^4.3.1" loader-utils "^1.2.3" +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + "@testing-library/cypress@^6.0.0": version "6.0.0" resolved "https://registry.npmjs.org/@testing-library/cypress/-/cypress-6.0.0.tgz#935f7716e0e495f02fd753a42621e4d350097dce" @@ -6202,7 +6214,7 @@ boxen@^1.2.1: term-size "^1.2.0" widest-line "^2.0.0" -boxen@^4.1.0: +boxen@^4.1.0, boxen@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== @@ -6493,6 +6505,19 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + cachedir@2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/cachedir/-/cachedir-2.2.0.tgz#19afa4305e05d79e417566882e0c8f960f62ff0e" @@ -6948,6 +6973,13 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + clone@^1.0.2: version "1.0.4" resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -7270,6 +7302,18 @@ configstore@^3.0.0: write-file-atomic "^2.0.0" xdg-basedir "^3.0.0" +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + confusing-browser-globals@^1.0.9: version "1.0.9" resolved "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" @@ -8183,6 +8227,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + dedent@0.7.0, dedent@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -8235,6 +8286,11 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -8976,6 +9032,11 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3: d "^1.0.1" ext "^1.1.2" +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -10351,7 +10412,7 @@ get-stream@^4.0.0, get-stream@^4.1.0: dependencies: pump "^3.0.0" -get-stream@^5.0.0: +get-stream@^5.0.0, get-stream@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== @@ -10520,6 +10581,13 @@ global-dirs@^0.1.0, global-dirs@^0.1.1: dependencies: ini "^1.3.4" +global-dirs@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" + integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== + dependencies: + ini "^1.3.5" + global-modules@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -10683,6 +10751,23 @@ got@^6.7.1: unzip-response "^2.0.1" url-parse-lax "^1.0.0" +got@^9.6.0: + version "9.6.0" + resolved "https://registry.npmjs.org/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" @@ -10852,6 +10937,11 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + has@^1.0.0, has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -11157,6 +11247,11 @@ http-cache-semantics@^3.8.1: resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -11929,6 +12024,14 @@ is-installed-globally@0.1.0, is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" +is-installed-globally@^0.3.1: + version "0.3.2" + resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== + dependencies: + global-dirs "^2.0.1" + is-path-inside "^3.0.1" + is-interactive@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" @@ -11949,6 +12052,11 @@ is-npm@^1.0.0: resolved "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= +is-npm@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" + integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== + is-number@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -12179,6 +12287,11 @@ is-wsl@^2.1.1: resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + isarray@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -13234,6 +13347,11 @@ jsesc@~0.5.0: resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -13410,6 +13528,13 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: array-includes "^3.0.3" object.assign "^4.1.0" +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + killable@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -13501,6 +13626,13 @@ latest-version@^3.0.0: dependencies: package-json "^4.0.0" +latest-version@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + lazy-ass@1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -14190,11 +14322,16 @@ lower-case@^2.0.1: dependencies: tslib "^1.10.0" -lowercase-keys@^1.0.0: +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lowlight@1.12.1: version "1.12.1" resolved "https://registry.npmjs.org/lowlight/-/lowlight-1.12.1.tgz#014acf8dd73a370e02ff1cc61debcde3bb1681eb" @@ -14639,6 +14776,11 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -15151,9 +15293,9 @@ node-releases@^1.1.29, node-releases@^1.1.52: semver "^6.3.0" nodemon@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz#9c7efeaaf9b8259295a97e5d4585ba8f0cbe50b0" - integrity sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw== + version "2.0.3" + resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.3.tgz#e9c64df8740ceaef1cb00e1f3da57c0a93ef3714" + integrity sha512-lLQLPS90Lqwc99IHe0U94rDgvjo+G9I4uEIxRG3evSLROcqQ9hwc0AxlSHKS4T1JW/IMj/7N5mthiN58NL/5kw== dependencies: chokidar "^3.2.2" debug "^3.2.6" @@ -15164,7 +15306,7 @@ nodemon@^2.0.2: supports-color "^5.5.0" touch "^3.1.0" undefsafe "^2.0.2" - update-notifier "^2.5.0" + update-notifier "^4.0.0" nopt@^4.0.1, nopt@~4.0.1: version "4.0.3" @@ -15223,6 +15365,11 @@ normalize-url@^3.0.0, normalize-url@^3.3.0: resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + normalize-url@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-5.0.0.tgz#f46c9dc20670495e4e18fbd1b4396e41d199f63c" @@ -15813,6 +15960,11 @@ ospath@1.2.2: resolved "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -15980,6 +16132,16 @@ package-json@^4.0.0: registry-url "^3.0.3" semver "^5.1.0" +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + pacote@^9.1.0, pacote@^9.5.12, pacote@^9.5.3: version "9.5.12" resolved "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz#1e11dd7a8d736bcc36b375a9804d41bb0377bf66" @@ -17187,6 +17349,11 @@ prepend-http@^1.0.0, prepend-http@^1.0.1: resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + prettier@^1.16.4, prettier@^1.18.2: version "1.19.1" resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" @@ -17459,6 +17626,13 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pupa@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" + integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== + dependencies: + escape-goat "^2.0.0" + q@^1.1.2, q@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -18415,6 +18589,13 @@ registry-url@^3.0.3: dependencies: rc "^1.0.1" +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + regjsgen@^0.5.1: version "0.5.1" resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" @@ -18670,6 +18851,13 @@ resolve@^1.1.7: dependencies: path-parse "^1.0.6" +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" @@ -20622,6 +20810,11 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -21166,6 +21359,25 @@ update-notifier@^2.2.0, update-notifier@^2.3.0, update-notifier@^2.5.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" +update-notifier@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" + integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== + dependencies: + boxen "^4.2.0" + chalk "^3.0.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.3.1" + is-npm "^4.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.0.0" + pupa "^2.0.1" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + upper-case@^1.1.1: version "1.1.3" resolved "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" @@ -21204,6 +21416,13 @@ url-parse-lax@^1.0.0: dependencies: prepend-http "^1.0.1" +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + url-parse@^1.4.3: version "1.4.7" resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" @@ -22058,6 +22277,11 @@ xdg-basedir@^3.0.0: resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" From 1b6a9538c1cf78b7747a6ca4d475a3a96ede053f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 14 May 2020 08:37:24 +0200 Subject: [PATCH 41/73] build(deps-dev): bump @storybook/addon-links from 5.3.17 to 5.3.18 (#856) Bumps [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/addons/links) from 5.3.17 to 5.3.18. - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v5.3.18/addons/links) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- yarn.lock | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index d8ec2fa50f..568d849ba1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3006,15 +3006,15 @@ uuid "^3.3.2" "@storybook/addon-links@^5.3.17": - version "5.3.17" - resolved "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-5.3.17.tgz#bccbd49108f03eb8f7a72106fbbceed671e042a9" - integrity sha512-g8PsDoHYEmgK1q1h+eLqXsWLhCj5CFyiZLrtomjCp1R0XpZA7PS8LyO5yHbxzEzEv68DHlU5rwQnNnkbGnr7XA== + version "5.3.18" + resolved "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-5.3.18.tgz#6b4aec83e158c000221bfe1f43e4a473f0727193" + integrity sha512-rDpsAQUr60BEM83TBh7mwKjaZvqTR+CAAMLAxXN+J8DMoOjymjd2sjq4UVJsOQVTnw3wjIc4+xpAL/eYqkJAIQ== dependencies: - "@storybook/addons" "5.3.17" - "@storybook/client-logger" "5.3.17" - "@storybook/core-events" "5.3.17" + "@storybook/addons" "5.3.18" + "@storybook/client-logger" "5.3.18" + "@storybook/core-events" "5.3.18" "@storybook/csf" "0.0.1" - "@storybook/router" "5.3.17" + "@storybook/router" "5.3.18" core-js "^3.0.1" global "^4.3.2" prop-types "^15.7.2" @@ -17648,16 +17648,11 @@ qs@6.7.0: resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.5.1: +qs@^6.5.1, qs@^6.6.0: version "6.9.3" resolved "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e" integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw== -qs@^6.6.0: - version "6.9.1" - resolved "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9" - integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA== - qs@~6.5.2: version "6.5.2" resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" From da034b0ef6c4d7991ba6b42070390d1aaca15e9d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 14 May 2020 08:37:41 +0200 Subject: [PATCH 42/73] build(deps-dev): bump @types/yup from 0.28.2 to 0.28.3 (#855) Bumps [@types/yup](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/yup) from 0.28.2 to 0.28.3. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/yup) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 568d849ba1..5e52a1f32b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4635,9 +4635,9 @@ "@types/yargs-parser" "*" "@types/yup@^0.28.2": - version "0.28.2" - resolved "https://registry.npmjs.org/@types/yup/-/yup-0.28.2.tgz#ea6597d7bd48ecea94b81f8de7fd60fba07a4628" - integrity sha512-VvXPMsvUnkDIWHFIalJROljLSv9m9TaXKng5sLFUkmuybgY64AeBbZ5tOPZ9sjSpqhJFeOXIc22JSQDO6SCONg== + version "0.28.3" + resolved "https://registry.npmjs.org/@types/yup/-/yup-0.28.3.tgz#387c35f9a6a36b8d3561f6601eb4e72518b92899" + integrity sha512-0Sir2LxOmupF8HBUvpJoZghLmOqKfZsBk1GYlMwSIccLDDUoN04LHvo0KzDp9qxt1IKf9Fudpj35SrJ8VqetkQ== "@types/zen-observable@^0.8.0": version "0.8.0" From dbf0ae6cbac114f95c3ef8657bbf8a80b9006bd5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 14 May 2020 08:38:26 +0200 Subject: [PATCH 43/73] build(deps-dev): bump @types/supertest from 2.0.8 to 2.0.9 (#857) Bumps [@types/supertest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/supertest) from 2.0.8 to 2.0.9. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/supertest) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5e52a1f32b..f051fd7cc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4495,9 +4495,9 @@ "@types/node" "*" "@types/supertest@^2.0.8": - version "2.0.8" - resolved "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.8.tgz#23801236e2b85204ed771a8e7c40febba7da2bda" - integrity sha512-wcax7/ip4XSSJRLbNzEIUVy2xjcBIZZAuSd2vtltQfRK7kxhx5WMHbLHkYdxN3wuQCrwpYrg86/9byDjPXoGMA== + version "2.0.9" + resolved "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.9.tgz#049bddbcb0ee0d60a9b836ccc977d813a1c32325" + integrity sha512-0BTpWWWAO1+uXaP/oA0KW1eOZv4hc0knhrWowV06Gwwz3kqQxNO98fUFM2e15T+PdPRmOouNFrYvaBgdojPJ3g== dependencies: "@types/superagent" "*" From da149fc19528486dd421e379d4865ef9b97c8245 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 14 May 2020 08:38:53 +0200 Subject: [PATCH 44/73] build(deps-dev): bump @types/jquery from 3.3.34 to 3.3.38 (#854) Bumps [@types/jquery](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jquery) from 3.3.34 to 3.3.38. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jquery) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- yarn.lock | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index f051fd7cc2..61eeb04110 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4187,10 +4187,10 @@ jest-diff "^25.2.1" pretty-format "^25.2.1" -"@types/jquery@*": - version "3.3.35" - resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.35.tgz#ab2cbf97e7a04b4dc0faee22b93c633fa540891c" - integrity sha512-pnIELWhHXJ7RgoFylhiTxD+96QlKBJfEx8JCLj963/dh7zBOKFkZ6rlNqbaCcn2JZrsAxCI8WhgRXznBx2iDsA== +"@types/jquery@*", "@types/jquery@^3.3.34": + version "3.3.38" + resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.38.tgz#6385f1e1b30bd2bff55ae8ee75ea42a999cc3608" + integrity sha512-nkDvmx7x/6kDM5guu/YpXkGZ/Xj/IwGiLDdKM99YA5Vag7pjGyTJ8BNUh/6hxEn/sEu5DKtyRgnONJ7EmOoKrA== dependencies: "@types/sizzle" "*" @@ -4201,13 +4201,6 @@ dependencies: "@types/sizzle" "*" -"@types/jquery@^3.3.34": - version "3.3.34" - resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.34.tgz#0d3b94057063d3854adaeb579652048fec07ba6c" - integrity sha512-lW9vsVL53Xu/Nj4gi2hNmHGc4u3KKghjqTkAlO0kF5GIOPxbqqnQpgqJBzmn3yXLrPqHb6cmNJ6URnS23Vtvbg== - dependencies: - "@types/sizzle" "*" - "@types/js-cookie@2.2.5": version "2.2.5" resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.5.tgz#38dfaacae8623b37cc0b0d27398e574e3fc28b1e" From 5ed0ec374a8ae9da715efa4e7b1254c9e0c8383f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 14 May 2020 08:39:16 +0200 Subject: [PATCH 45/73] build(deps): bump ts-loader from 6.2.1 to 7.0.4 (#838) Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 6.2.1 to 7.0.4. - [Release notes](https://github.com/TypeStrong/ts-loader/releases) - [Changelog](https://github.com/TypeStrong/ts-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/TypeStrong/ts-loader/compare/v6.2.1...v7.0.4) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/cli/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 3f08feb738..8e485dc5ac 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -62,7 +62,7 @@ "rollup-plugin-postcss": "^2.5.0", "rollup-plugin-typescript2": "^0.26.0", "tar": "^6.0.1", - "ts-loader": "^6.2.1", + "ts-loader": "^7.0.4", "webpack": "^4.41.6", "webpack-dev-server": "^3.10.3" }, diff --git a/yarn.lock b/yarn.lock index 61eeb04110..a2c9f54c82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20945,10 +20945,10 @@ ts-jest@^25.2.1: semver "^5.5" yargs-parser "^16.1.0" -ts-loader@^6.2.1: - version "6.2.1" - resolved "https://registry.npmjs.org/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef" - integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g== +ts-loader@^7.0.4: + version "7.0.4" + resolved "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.4.tgz#5d9b95227de5afb91fdd9668f8920eb193cfe0cc" + integrity sha512-5du6OQHl+4ZjO4crEyoYUyWSrmmo7bAO+inkaILZ68mvahqrfoa4nn0DRmpQ4ruT4l+cuJCgF0xD7SBIyLeeow== dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" From b23c6d3276e1a497d6f2a4a1cca1cf7d666c65fc Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 14 May 2020 06:44:01 +0000 Subject: [PATCH 46/73] build(deps): bump rollup-plugin-postcss from 2.5.0 to 3.1.1 Bumps [rollup-plugin-postcss](https://github.com/egoist/rollup-plugin-postcss) from 2.5.0 to 3.1.1. - [Release notes](https://github.com/egoist/rollup-plugin-postcss/releases) - [Changelog](https://github.com/egoist/rollup-plugin-postcss/blob/master/CHANGELOG-OLD.md) - [Commits](https://github.com/egoist/rollup-plugin-postcss/compare/v2.5.0...v3.1.1) Signed-off-by: dependabot-preview[bot] --- packages/cli/package.json | 2 +- yarn.lock | 94 ++++++++++++++++++++++++--------------- 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 8e485dc5ac..4e5911c5d2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -59,7 +59,7 @@ "rollup": "^2.3.2", "rollup-plugin-image-files": "^1.4.2", "rollup-plugin-peer-deps-external": "^2.2.2", - "rollup-plugin-postcss": "^2.5.0", + "rollup-plugin-postcss": "^3.1.1", "rollup-plugin-typescript2": "^0.26.0", "tar": "^6.0.1", "ts-loader": "^7.0.4", diff --git a/yarn.lock b/yarn.lock index a2c9f54c82..5849c6d8b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7268,7 +7268,7 @@ concat-stream@^2.0.0, concat-stream@~2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" -concat-with-sourcemaps@^1.0.5: +concat-with-sourcemaps@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== @@ -7940,7 +7940,7 @@ cssnano-util-same-parent@^4.0.0: resolved "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== -cssnano@^4.1.10, cssnano@^4.1.8: +cssnano@^4.1.10: version "4.1.10" resolved "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== @@ -11464,13 +11464,20 @@ immer@1.10.0: resolved "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg== -import-cwd@^2.0.0, import-cwd@^2.1.0: +import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= dependencies: import-from "^2.1.0" +import-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz#20845547718015126ea9b3676b7592fb8bd4cf92" + integrity sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg== + dependencies: + import-from "^3.0.0" + import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" @@ -16061,11 +16068,6 @@ p-pipe@^1.2.0: resolved "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9" integrity sha1-SxoROZoRUgpneQ7loMHViB1r7+k= -p-queue@^2.4.2: - version "2.4.2" - resolved "https://registry.npmjs.org/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34" - integrity sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng== - p-queue@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/p-queue/-/p-queue-4.0.0.tgz#ed0eee8798927ed6f2c2f5f5b77fdb2061a5d346" @@ -16073,6 +16075,14 @@ p-queue@^4.0.0: dependencies: eventemitter3 "^3.1.0" +p-queue@^6.3.0: + version "6.4.0" + resolved "https://registry.npmjs.org/p-queue/-/p-queue-6.4.0.tgz#5050b379393ea1814d6f9613a654f687d92c0466" + integrity sha512-X7ddxxiQ+bLR/CUt3/BVKrGcJDNxBr0pEEFKHHB6vTPWNUhgDv36GpIH18RmGM3YGPpBT+JWGjDDqsVGuF0ERw== + dependencies: + eventemitter3 "^4.0.0" + p-timeout "^3.1.0" + p-reduce@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" @@ -16098,6 +16108,13 @@ p-retry@^4.0.0: "@types/retry" "^0.12.0" retry "^0.12.0" +p-timeout@^3.1.0: + version "3.2.0" + resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -16483,6 +16500,11 @@ pify@^4.0.1: resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pify@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" + integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -16831,7 +16853,7 @@ postcss-lab-function@^2.0.1: postcss "^7.0.2" postcss-values-parser "^2.0.0" -postcss-load-config@^2.0.0: +postcss-load-config@^2.0.0, postcss-load-config@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== @@ -16989,10 +17011,10 @@ postcss-modules-values@^3.0.0: icss-utils "^4.0.0" postcss "^7.0.6" -postcss-modules@^1.4.1: - version "1.5.0" - resolved "https://registry.npmjs.org/postcss-modules/-/postcss-modules-1.5.0.tgz#08da6ce43fcfadbc685a021fe6ed30ef929f0bcc" - integrity sha512-KiAihzcV0TxTTNA5OXreyIXctuHOfR50WIhqBpc8pe0Q5dcs/Uap9EVlifOI9am7zGGdGOJQ6B1MPYKo2UxgOg== +postcss-modules@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/postcss-modules/-/postcss-modules-2.0.0.tgz#473d0d7326651d8408585c2a154115d5cb36cce0" + integrity sha512-eqp+Bva+U2cwQO7dECJ8/V+X+uH1HduNeITB0CPPFAu6d/8LKQ32/j+p9rQ2YL1QytVcrNU0X+fBqgGmQIA1Rw== dependencies: css-modules-loader-core "^1.1.0" generic-names "^2.0.1" @@ -18825,14 +18847,14 @@ resolve@1.15.0: dependencies: path-parse "^1.0.6" -resolve@1.15.1, resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1: +resolve@1.15.1: version "1.15.1" resolved "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== dependencies: path-parse "^1.0.6" -resolve@^1.1.7: +resolve@1.x, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.15.1, resolve@^1.16.1, resolve@^1.3.2, resolve@^1.8.1: version "1.17.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -18967,24 +18989,24 @@ rollup-plugin-peer-deps-external@^2.2.2: resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.2.tgz#506cef67fb68f41cca3ec08ca6ff7936b190f568" integrity sha512-XcHH4UW9exRTAf73d8rk2dw2UgF//cWbilhRI4Ni/n+t0zA1eBtohKyJROn0fxa4/+WZ5R3onAyIDiwRQL+59A== -rollup-plugin-postcss@^2.5.0: - version "2.5.0" - resolved "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-2.5.0.tgz#54ef2487cda498d8bca69aaca3b55b30dee0106b" - integrity sha512-tBba0iMOY+eH1bP2rUhO/WK45uTRdRbuM5yWViO7tUChUrgA+JSQJscpCpStebPZoFxRwfkJRk2PZHd1q+JY2A== +rollup-plugin-postcss@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-3.1.1.tgz#eb895bd919285aaf6200324071103b4d3ea68607" + integrity sha512-4/FO5/2O5kv2uWRd7PPTN4mBCWoHwwFTnpEGokfPKfj6kygvTORqkBWNgVPXi7bBefNKtMA3FqQ10se6/J8kKw== dependencies: - chalk "^2.4.2" - concat-with-sourcemaps "^1.0.5" - cssnano "^4.1.8" - import-cwd "^2.1.0" - p-queue "^2.4.2" - pify "^3.0.0" - postcss "^7.0.14" - postcss-load-config "^2.0.0" - postcss-modules "^1.4.1" + chalk "^4.0.0" + concat-with-sourcemaps "^1.1.0" + cssnano "^4.1.10" + import-cwd "^3.0.0" + p-queue "^6.3.0" + pify "^5.0.0" + postcss "^7.0.27" + postcss-load-config "^2.1.0" + postcss-modules "^2.0.0" promise.series "^0.2.0" - resolve "^1.5.0" - rollup-pluginutils "^2.0.1" - safe-identifier "^0.3.1" + resolve "^1.16.1" + rollup-pluginutils "^2.8.2" + safe-identifier "^0.4.1" style-inject "^0.3.0" rollup-plugin-typescript2@^0.26.0: @@ -19006,7 +19028,7 @@ rollup-pluginutils@2.4.1: estree-walker "^0.6.0" micromatch "^3.1.10" -rollup-pluginutils@2.8.2, rollup-pluginutils@^2.0.1: +rollup-pluginutils@2.8.2, rollup-pluginutils@^2.8.2: version "2.8.2" resolved "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== @@ -19096,10 +19118,10 @@ safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== -safe-identifier@^0.3.1: - version "0.3.1" - resolved "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.3.1.tgz#466b956ef8558b10bbe15b87fedf470ab283cd39" - integrity sha512-+vr9lVsmciuoP1fz8w30qDcohwH2S/tb5dPGQ8zHmG9jQf7YHU2fIKGxxcDpeY38J0Dep+DdPMz8FszVZT0Mbw== +safe-identifier@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.1.tgz#b6516bf72594f03142b5f914f4c01842ccb1b678" + integrity sha512-73tOz5TXsq3apuCc3vC8c9QRhhdNZGiBhHmPPjqpH4TO5oCDqk8UIsDcSs/RG6dYcFAkOOva0pqHS3u7hh7XXA== safe-regex@^1.1.0: version "1.1.0" From 359649086f6b9f940b82064b755352951e17b61f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Adaszy=C5=84ski?= Date: Thu, 14 May 2020 08:46:45 +0200 Subject: [PATCH 47/73] Add component table front-end (#848) Co-authored-by: Wojciech Adaszynski --- .../CatalogPage/CatalogPage.test.tsx | 4 +- .../components/CatalogPage/CatalogPage.tsx | 49 ++++----------- .../CatalogTable/CatalogTable.test.tsx | 56 +++++++++++++++++ .../components/CatalogTable/CatalogTable.tsx | 60 +++++++++++++++++++ plugins/catalog/src/data/component.ts | 23 +++++++ .../catalog/src/data/mock-factory-data.json | 35 +++++++++++ plugins/catalog/src/data/mock-factory.ts | 31 ++++++++++ 7 files changed, 219 insertions(+), 39 deletions(-) create mode 100644 plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx create mode 100644 plugins/catalog/src/components/CatalogTable/CatalogTable.tsx create mode 100644 plugins/catalog/src/data/component.ts create mode 100644 plugins/catalog/src/data/mock-factory-data.json create mode 100644 plugins/catalog/src/data/mock-factory.ts diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 02b7fcacff..01d821e175 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -16,19 +16,17 @@ import React from 'react'; import { render } from '@testing-library/react'; -import mockFetch from 'jest-fetch-mock'; import CatalogPage from './CatalogPage'; import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; describe('CatalogPage', () => { it('should render', async () => { - mockFetch.mockResponse(() => new Promise(() => {})); const rendered = render( , ); - expect(await rendered.findByText('backstage-backend')).toBeInTheDocument(); + expect(await rendered.findByText('Your components')).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 5d530f8416..9972b9d71f 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -15,48 +15,25 @@ */ import React, { FC } from 'react'; -import { Typography } from '@material-ui/core'; -import { Content, InfoCard, Header, Page, pageTheme } from '@backstage/core'; -import Table from '@material-ui/core/Table'; -import TableBody from '@material-ui/core/TableBody'; -import TableCell from '@material-ui/core/TableCell'; -import TableContainer from '@material-ui/core/TableContainer'; -import TableHead from '@material-ui/core/TableHead'; -import TableRow from '@material-ui/core/TableRow'; +import { Content, Header, Page, pageTheme } from '@backstage/core'; +import { useAsync } from 'react-use'; +import { ComponentFactory } from '../../data/component'; +import { MockComponentFactory } from '../../data/mock-factory'; +import CatalogTable from '../CatalogTable/CatalogTable'; -// TODO(freben): Connect to backend -const STATIC_DATA = [ - { id: 'backstage-frontend', kind: 'website' }, - { id: 'backstage-backend', kind: 'service' }, - { id: 'backstage-microsite', kind: 'website' }, -]; +const componentFactory: ComponentFactory = MockComponentFactory; const CatalogPage: FC<{}> = () => { + const { value, error, loading } = useAsync(componentFactory.getAllComponents); return ( -

+
- All of it - - - - - - ID - Kind - - - - {STATIC_DATA.map((d) => ( - - {d.id} - {d.kind} - - ))} - -
-
-
+
); diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx new file mode 100644 index 0000000000..0ab95f4231 --- /dev/null +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import CatalogTable from './CatalogTable'; +import { Component } from '../../data/component'; + +const components: Component[] = [ + { name: 'component1' }, + { name: 'component2' }, + { name: 'component3' }, +]; + +describe('CatalogTable component', () => { + it('should render loading when loading prop it set to true', async () => { + const rendered = render(); + const progress = await rendered.findByTestId('progress'); + expect(progress).toBeInTheDOM(); + }); + + it('should render error message when error is passed in props', async () => { + const rendered = render( + , + ); + const errorMessage = await rendered.findByText( + 'Error encountered while fetching components.', + ); + expect(errorMessage).toBeInTheDOM(); + }); + + it('should display component names when loading has finished and no error occurred', async () => { + const rendered = render( + , + ); + expect(await rendered.findByText('component1')).toBeInTheDOM(); + expect(await rendered.findByText('component2')).toBeInTheDOM(); + expect(await rendered.findByText('component3')).toBeInTheDOM(); + }); +}); diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx new file mode 100644 index 0000000000..31f15fe5ca --- /dev/null +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -0,0 +1,60 @@ +/* + * 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 } from 'react'; +import { Component } from '../../data/component'; +import { InfoCard, Progress, Table, TableColumn } from '@backstage/core'; +import { Typography } from '@material-ui/core'; + +const columns: TableColumn[] = [ + { + title: 'Name', + field: 'name', + highlight: true, + }, +]; + +type CatalogTableProps = { + components: Component[]; + loading: boolean; + error?: any; +}; +const CatalogTable: FC = ({ + components, + loading, + error, +}) => { + if (loading) { + return ; + } + if (error) { + return ( + + + Error encountered while fetching components. + + + ); + } + return ( + + ); +}; +export default CatalogTable; diff --git a/plugins/catalog/src/data/component.ts b/plugins/catalog/src/data/component.ts new file mode 100644 index 0000000000..5b67b647e4 --- /dev/null +++ b/plugins/catalog/src/data/component.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. + */ +export type Component = { + name: string; +}; + +export interface ComponentFactory { + getAllComponents(): Promise; + getComponentByName(name: string): Promise; +} diff --git a/plugins/catalog/src/data/mock-factory-data.json b/plugins/catalog/src/data/mock-factory-data.json new file mode 100644 index 0000000000..8fc61a87a6 --- /dev/null +++ b/plugins/catalog/src/data/mock-factory-data.json @@ -0,0 +1,35 @@ +[ + { + "name": "example.com" + }, + { + "name": "subdomain.example.com" + }, + { + "name": "subdomain2.example.com" + }, + { + "name": "User data pipeline 1" + }, + { + "name": "User data pipeline 2" + }, + { + "name": "User data pipeline 3" + }, + { + "name": "Aggregation CRON job" + }, + { + "name": "Authentication service" + }, + { + "name": "Payments service" + }, + { + "name": "Backstage supervisor" + }, + { + "name": "Identity service" + } +] diff --git a/plugins/catalog/src/data/mock-factory.ts b/plugins/catalog/src/data/mock-factory.ts new file mode 100644 index 0000000000..010036d732 --- /dev/null +++ b/plugins/catalog/src/data/mock-factory.ts @@ -0,0 +1,31 @@ +/* + * 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 { Component, ComponentFactory } from './component'; +import mock from './mock-factory-data.json'; + +export const MockComponentFactory: ComponentFactory = { + getAllComponents(): Promise { + return new Promise((resolve) => setTimeout(() => resolve(mock), 2000)); + }, + getComponentByName(name: string): Promise { + return new Promise((resolve) => + setTimeout( + () => resolve(mock.find((component) => component.name === name)), + 2000, + ), + ); + }, +}; From 2d3412cf34e499ce60f8120ba466df1e20683dbf Mon Sep 17 00:00:00 2001 From: Kat Zhou <61153904+katz95@users.noreply.github.com> Date: Thu, 14 May 2020 09:08:06 +0200 Subject: [PATCH 48/73] Contributor Header (#580) --- docs/contributorheader.png | Bin 0 -> 123023 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/contributorheader.png diff --git a/docs/contributorheader.png b/docs/contributorheader.png new file mode 100644 index 0000000000000000000000000000000000000000..53d1027d85eba784c90657e530b6ed67c52d3557 GIT binary patch literal 123023 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�@8l`?_cGBWS;xm)Al~3^T0rXNg(o4-oncXSZ<<& zq2kWDzg`dmQVkOjPqL(~JcbE`Pt`C-6d#;=Y;>n8b+WXE(2ZJ)Kp)C)>uvVtR-9gIi&n447as?Z0vv zQBjS1?C65F%1@vA(?d81OwJ`ZbGjdvoXZsZ%Rjd|(3u8{=AZ9i@7%@4OXXL(^u7tP zdGb1v6TVz*RW3}e0Ec`3-JA6neQPXAww1L|NDQC`PtX|cWn*asSVk7uYdUGYOZ?jo*dniU&F)9M{&D<`G!dH z!5=z*7WX%CEKzaT0g%lIE#baI(U43XX-b5#n1WBZ{uMo5C{PuqgS1Hg$X|o+XhDw^ zHJr&m_~plsAALfwsuy3#~qkI9cuypK)O^;&$MD zVi?lPp!TYHyMt@7vgJ6#?PzGI-76n;b{@^{_`r^^NJOmoha9ZLC`TYxUrkLM7=~S* zdnbAoe15ojYF|JRfh*$kO!YcbTmno z7+yNrz#l6tb`kKahllXpqBMXT1RscGo8XBATtBLX5OVNKqUBdGTdMc2&Cw|4_|--h z_er+gQ6+p}|2W1rG=J*%m~8m+wgU4UIDcnrW#USP7(KRQJ`XPqWbJj}PIiflo2XzV zvu5k!h}jjIfjhP*`-k354|*+EtB^J+2Ij5JAl*}2htxpJ}4b(@h&ZE-vi*ph?%2HZ0Geobd0asz; z=xCy9lTna!+>>;iWlV63HNw+GfNc;~F`9siXiQV4avWZ|uHA2B$B*tk&N5LS-S<;v z?+(}qts&>fMQr@B;i3BC!}!*&!bO~UZ)*I&1h<(C?%_GBzO@P8UANzwfNxP`X|T?^ zhn6CmE7ikrDflrPDMUAzyVcyzmZ3WBgVXrE_^kM*pew1xm6qOFxZF4+82eFcCEUGK z*aQ9Ey)n(1f%pa0GHqx77;DGlp}i|4K{6%)ZgFG%7>ObzY=>AIB=W3wfo-Z#cehTt z+2mv;hGi(^*Pi2|Dm(aHxnj7qJSLa|491^MtloLL*Sc7O3CzoHEY_c>@E6mo^gcw~7UZZ)Dxj->fhRw?=%do1KSf0hg55XU6(+-$;eXV;=|9e9aoa-rwqP;jb>fkfO_l~AYYH?zp9#&`|*kIYR~&cIkr!h zX_CWBYa<>XEz|H_Y8CV{>+S#(MdQBsop+h4skT~m5XjB>cdPE25)zjt^~@62h$yVz z+gsZEA*J8()s|^x;AQO@vo@A@@GiNPmMXokyzrb%C|Y1B{1JDo!e%NuZPI6cb(0Tz zhi!Xg;dL(Cge6sAz^oTWd7rXzjP`EByD9rRwmLm5fP)_wi~2 z4(Pfk8{jboWyiOncwqWg5gWtx<5}h_0Co z5-=+(_b;VEr(?iG@1+N}qyZ>9%*SB?LH%c$3o~>tzB(uNaZHf#+S3na?YA1>>{EO7 zE@qz{2wq@{uuX&E3`|p{Lvzaxe01szvlpGqL%9lpW3|dWH_K-wf$PRNHR>zT(%;p3VRDjUb`tp@BK+s$ezf#!?8ao$y*pluM!y0!`&kH*@1 z*6?cFNWI;~~M(=tVIwNtU<#&0zCS z9TXQ6F+=4?7#9}cit4P3YbfR&{jvo-D)bKKZE0zK6VBk4#M^BqCO~Nw)C@LQJpwKa zyZ62goAB7)sXOpk+{GikE#6iNHx8wq$MUosRYbfj9Ih!DdKFiFkl{GtHqL|>qkS_M zy85d3(eM&hGS{yRfv&56z;p-Ms}U%(1LElW&{|<|O|)Z;rPW=c(gPImoB@YTRs{CE zp{~Vc_riMsXJ1J_o|aXqyW7_e;P>XeiKO1A)tsNvJU;(5pEldNM{;mwl81}K_D{K6+*j1Cym2$O#ovK14ODL?6&Nh_&LS=xk9Slb zRHTTQG^g8$>nh?`dG)WCHy?Z;9~3}h2mVde6efO85wt^yEGD(%Yrf!kK?0W+R!NCd zx=Z$An0FqBlB0iAfifv%_G+n8cxYFb3~f&<4eD_XFM%`b@Re;v!%UPjU)Q>I@$wf= zGluN8X?|V~pD*0@AZ?$h50I!%_6typl^u$b&*fE(35mrqdT3`&8Y!WY;nLeB^0}mN z{84u>3TMg?U{~xnXJ^llt)Tpp@0 z^sHBV8w}qmp^&B*JUO)e=YO3A?{_MVo@mTi`r`M0e*ys!G)bB&t>1Wl8~ey^gwi86 z-oc?_tIB1zR~e#`iTr~A7ZMJMPxSG;r172vOlLir-xGgxz8%%s(ZPGXFZyg+CZXo_ zw}!g9{dGM07dY{Hi0qKJnKdS1?JXKZvG)F{yQYEW%=q&VwONTu#aUL5zT3;DyPPb` zria0vo|nkDhct;(53N(au5AVq&e9Rie!@{fk&UZrF6~ooIX*jBR9t17*F;4-b~9km zFMK5Qb6^1#lxh(a&ez+>_&IPk#-Ko|tvoe@*MW*2Owb+>)Q+3$>&0*LU1rAT1~H&_Pg$nz8!IpH z2;Sm5&xhs>CY{uqtmEsY%xP|}xZUxL%fCgyt(~?y4@Xbd5W@$uDAnoE4Fw~(4pdeR z8I+?|k3XrOS;&Ku zTwICXO3A14#PC0`TBf_~d zEq+6IeS9=s=oEa|4s_>;0E!RF%YBoSrJv9?9QIE^d4~vlwn}JP(v{HbE7kzt_ya|o zojQPY3g}$0YnSJ5HtUS8q*)xwERL=09L>`gjQJ1{ff;*C`uv-_Qk+Dx#Q1kM>a43nM0)7%l}b!3{r9RxwWg+}n*{(th!69HHYfbA>uF zG543y8|_<#No#=?Vcr9S8%=nWerjBxF=4HbgDOYeIqC2ZLqyP2Gn13Gv$u770k(LN zILV(Z@bc>&6j$gt_B6Ot*NVv{Nmh@Ab9`&&qhe^7o3gH;Bq(SU9^VNCg&o77|p#ElK{4UD8f}RwDa^RP`AtJtbcI+5?qu z%y`itF!w>M(+ya$#Hpx__RtTtg}p+p_JU-rk3I3ck?sK^31Z4rx);*0JJ@heN9LQr zT)EMd9RLHaF5_5}ra_Og=3S;!>R<@XBE&abgmRASMXL4wxOqj1Ctei@h9Zx6Rf0EW zGvX}cjn*d`0$E}jLbgAMCHW((XUG#LFxhI$pK^qdxdXNI-KDmp(rEudY_5%Iks4N3 zMnNIX*N3>XYIA(t;TQWAAH+6k-hdA6q(XS3=kzDq?-?Z;@nXs654M+!+?>P9b5G?j zM5Ce-5)v?>CA7KUJy+5*KJlX89RHB*eRp}bUe{_nkt2Vv2~$n5l;bHLAnN%L6N4hq z$5*hQFu%56{ytY{Yv23blWDmRoV(2vyALKlc$ibT%-$~wIawa>v#V>ud5_nceSf06 z`?yIg_0DUoA6uQ!o8i^iCa;y?8ClEi^zJ4pdOdr%0h-4+acMn~=V{~k604C7vhs=F z*J-(zG|=VkeDXnhfaBf|omT&FnMJ;+M$uaDaz4e5S2DK`sAXu&*4^bT)jt*28=G@u zbMBuPjy3b-l1oXFlkz^AdA7tv>ly4gMJH35wpotF=S<8>#ajD;xr>o~QulL@c7)iD zQC-mzkBlLmX|a?>HBQa0gvF=%3Trj5FT%yOL%~K#!RXR*ph2Ed^xLJQaz8!4xt;8Y zgT(@sGc`+J(`Oct#HM^XgFIe2Gqw7?2RBC_{5vEJ;X?Fsmk7$xLI6Xpqh&};`(yr` z4!S$r)5%i$k6%pzB2y($Qd3mf%m6du>FayB?;?86%9y4412cRoccJK%cwI?!o|rHk zofbIMhvtP(s^sgC(I-(FIavr6@Y>5%l)KhqAAoX1()mL)}cF6S6KCMS;|u+Fz%1Fgf82&`(QmgQ_>}U3z}Wtq$NEDsA*2 zDJbofuAElit%G>SLucGq3r(v!ERHX2sE_P&Gj8N9Pa^+ts^~^sZ}-=)PXdP`k@saM zm7pkzonC@aG&#L_qd5TS_t(QD%Y6NH+Fp&|+{;cuP*XtXXqFN+J1%K&Kdy4X;84AN zR2>1&{rMYL6458E;#^Mf+uA3O?28M(Ua{7ExbYoxiZzz_d~csUkZVUB()cZ^;i*Kc zoWwS<_10E~>eU2{X2@!nsWH-o;rh+LPH?SMP$jMuhVjPy|9A$x8cAJX=j( z`7%wa7Pca=D|rGKNDS^!(&25*q1b zZD_&ELn?&ktXCQOtkr@-l}NO<1G0DYh`5^t*AqmP5jGQ0vXFFbZA|Qw0VxC_9z!Gl zIZ#|2(nt~0@RW&5jfwwit`p5In4eQtQX=@s5L9QD-ZtczK}AZ)r=zu|1Ea4p+f}|3 zKY%aM{Bxe{qixAGf|s4O^+#bcEfyD*X9aVjm?6v{1kL7)jxf7OnPe*QN5gixFd@OB zoM8u<2@Z#0GdOOzH!c)fFbR}3Oc~3_Yv{W#S6@Lx4!zMQbB|ZD<6ax9_Q>MbQq(v6 zb}uHrdMSYB1q>!{Wmm|`{`%#8x5_o`uom3ViyVfJmv$Vj%Ip?Pu5m6yZT)JjIzQgo zq0`5zU=TD`7F!ulu>~!)nR07xhcQh~@VDw;( zi}VYtWYnw;;Q&#$eA3oVX`$1tg3ZEyea}g#Yzf}Xhi;q}(|%u?Ym3r8Jctc6qAZt} zf_yCu)v`bx4{E#7Xltlb^pBeu1XZOghBm=`9`)wg%l-2qMsdo+^)%EZrs`QRvfIw9 ze?c7jDM)8(d);~NkMn^T_p_^W6CItMEOTPQ@;aB_7Y(ltt2OCp#0g8(YwOjh$L($3 zFx1+95Sa-xH(4!@yuFxRxH55rA^$xnfL_6_Y~NJn!X?eeyA)MeG-+!Z!JG^Y4Fw@5 z7ktrr)kOViGx=9E(2_BkyMfX}?jKv`2NhLSsUu7Suf+81>~zWBN0SFLndWvO<8Kpx zhDg-UOT320O7zcHrcpDH*1ep*@=?(vrseBE2Jyj9=caJ}dGs_FTAZvRUrzZ;|+I4{fuUy|N2`lAY ztu>mA3)p3!)F3dFr>{fH*clIoix zgyj38FJ%QkkV8LV6)_3KFLiZw+Li5<@%MFPWo6?+;)Q?apH?s!S5y8?@gg2;IGvqx z)$I8LI3+X~M?T2LEUjh@>U~V&TXz7!U(xffsxQ`{>l8<=b$;3@(e&x7JgfuH1TEz< zB>OeEU<0RD3Lf7U-)VSrByTSGLAR3oOcn$cR$|95bmr9|b+gt8OA?$u?&K4OKA)4W zazn0_BgL+%Uo@TJu;BMQpCAn$Fy8c$yT)L*hBQ1{wRwVQ+Uhrm^CTqp-SY@Sp6=S) z@SBrHQ?d`37RB?6bu?G<_$O1pDS0jT=LQ>xFmd8h?0qEycUB%5RG6pet!>zbsOG_* zHDPi;va9obKMy15v1{+8^O(ewA)h|7pOok~cTLXFryO~yux8E5lTko9 zXZZ_e0386sbLsu5>JT4li=Tb!gt~rx*2w&LS&d98L$!ORBx1zP8ZqiTFXvY9v;nir-Z+Jgz5Asi^;m9pEcb*RMjR=-R3Qn@L-mvBT;1HreA{W zuJCH63Jf*>)-_T9<4~4c*w))LkvsBrLJ=}zNnn!iavI5fJ&I?AO!Gn9e=Cdm;x*Nr zHLd9UWDT4HS&%+7Oq%cXA3imUHCV~D{t=2aGhIkQ1q~B^n4r|NNNWi#eSJgu{A#1E zIw8&^sOGNV%bQWMau9S+?9JqhBt;KhrlrwJ7xbt>PKAX7|E_aDz|A!}DBTB#7(`x6-mGZP4!gw}*rZB+2AcI;9T|>0mN3v9O z0Vv#z)cpXN>Qxx)9RGtg?}i2Pnr%JsriY#*$p`;=XsTpws6i!a&ZOPX5h<>JADkDS zRPJ*03CYw~QCNsE$E|oq-fr+;d>ME;OJ4V~BIqA~zGkdq!&KBCi8XDZ5uAkY9Cnim z3b-Nn?c+%vT{I|X=e>ifg_Nfr`H(5(lh$p_h1fi>IKvC1o*|+GtMx&kB+37iNcyM z73$|EoEPENX>23emf(m){kH8)-!Bug>&>3hLG&4*b%4Lth^pIzq6L&^2}9LOzboVa2EJ~e7yJFz zW4*K3uau(kdELHO{2gwC+>&S(sGM
l8w*AA#2Y%Ir!53ayO)|D zms4Qi84`E?4vg5ESF1{#sUV(Ra&!GPleRFG;a*vpj~P*fiVMatH~_S=UnsJ*9Hb50!ad|7dlKYQLOC5j_V z03VE{RaBpmTvv$sX4cfAUWp!d4T81=UIzaW#g_bO^zlbGsdDR#->y4N<1~2x{(UfR zMWiWN8{nnHh}xfpb;bW`RR^aN)lrXgL;Twy4H`AlXz zU^prwU%R^D_2Gt^YW1S(+xxkEAZa7ywk)S2SGs~!J@uRx74To!}Jc~fE88h`i&lH zvoyT_>tlv+{M5`huB}jdXv=`iy-iGG71W8P#g$9?*>q&8oo+OfxGICPA4!E&>5CO? zqy>TsGxTk$cd4N1^L)gSPGz4*98bjxV=~gy&Bo6_gVAqUtF8_5YBPas84k<2l4%@4 zR0~=^Us_%+0`@Y<`*$TvZ}`gzm=M%iI9yB#4S6am(ifBU^Tnv40M?#RxhIrRPFid- zvQk#m+?0V0Rje3K1`TSC-w{`T?}@$W+|_2Z<9fKZ=vchq|}rMIDz!$f*jY8A~a@qOJC40q=7E^X!ECgeSze31UZ!y0PCU+ zhAzPMqD&sdTfgo232E4Cn`JlADp}jv`Fk@!sxQe0sZ&<^e}gbb&hZb(z7_3Kp4Cn_ zgWOv3yN08o33X!IvrCYXABY{Gye(}=V&y&nA+#YF1aD^(W}V;{6fJnD)eP*Mj{xaw z_X2)!5rDEcKlyn9$5BsT~!*6*e0@{QUfMrt{<%1 za+v_Y)B17NA?TdTMZ#5(xqyBCj!EGbGE=jK2(@aGW}UW>cu7i1BytA1c2BbQ+pWXn zWh@Q%tHfE27DG5yPM4rAX{N5Isb%@H#jRnVf%NSgmtdgGkrj(Eu#Zlb1i|&Of9FV1 z5*?Vw&Qi<=!my>4l}d!8qa%FlB%D$nTO1?_A}BeJ01W>7l<>byCDrSVvbhvk646%? z3G!9S)-Tu}+_>32WTjsi&pSm4_Nh*=l6mZ1ec-$ubXOI0V_KBIgBMq)$S|*)tfzDk zC;t?SXx{UMGiXSXxQ862YvI=xM7*V&JDW$F@A@N;_UFE*u|OJtx^PA)xln-?-s*#v z6HXbA7LhE=JulV_G8d|XwtB_Y<^`m|=xci3sdI^Yw{)+rZZH30Y0U%Jw}nk4=g9;K z-U?s|MRNbjw?4@TI`>CaDdf(bJ6T6ME5D%;2id&04w-+Hjm=}ntsEA{d+#zob6%GRv4#XrA!B^%(eg+`c&U zcn2AHod@qUqXyeGQmTKn`et+N6p7dI6L|rtravCqK6%$7ILe2Hd4CIBZpg4J%I(;A zC+(kqU}%YoeT5NgFL9FhaEG6J(%vI~Uer@<_GH#J*QnM{Ap#6(1UH zGC55MVE7;^;r)@gIW2j&HkTH+I8dPHn?wc4fe;tnujbXF?2u2lRzIOYpXr+_J_ zOnKoaj~x=Hq@eJN_G0koqxhrfILXw~g-8zcQvwdyo6qE>WNsFGWbWo$G1E47_&|`$ zDPUSFR)SaUS`bYyXLI)ONwT5I=Q&+w_16>d+$R3*f%mDQ9ItwjGw5Ka5uY9B{ufs( z{~%E=UOqduF?Xd2q0BzfxnFL+I#EGlt8zJR-j;!k`~jJ`-3dE6vousJ;87oJ5sJTx zREu*S1RWhslX5hMAWbrNt#po0j{ZxO92S&Zc2BknLfsAQ8~V> zGW?I)G^n|vm-nqoR9iw8jA7aVaoAfQFQ}YtBudq!vx=@$mWphKksxf^z&0s!wwOk0iG9I&7ZueN^{V5;pVW<&|oE7{UZ7LZ%)ZXStY){XX$MA!ac8&!c1 zLd;nqbiU9))v)COKugEEzVSV&uu4`aWUP)@(|{ts{$@@PtP5>9^_iUhDtl#Kyg{x@ zc5r@5=GdTfJ2qEBa-oDrk0n9Mj@-{qhf!HQg20MT{(v+IjSJLjWXJNb>+DQ29K}oS zLQq|N`!b-$DJ+fK308DCjHuT}pkW}7=crb^0}z!uT=sJNsKC5KfuCMrW>ez8xs2%_ z>9S%p`6ym{LS0htP4oDaJTcEhd?4^oJUZ&Bk2UmIa7p>NGaX9k=67(P6Q&d}Zs3Dc zDS8orl}Vf9SpS;Gh|&(3Tk)CF5gB>IZX|tW-<3(9VA7)g&4{EF%tPTRYjqrn#jnP zVp1_Frr*t2dD4HcAB*N|EOvwe`3o69pm6h_4fz)>GgJIxY3buhY(IMQ-PyJ)?V~eO zLT@ZLT!9ZQ>KX6lE8A-vuk{hAzGdh;d`K55?uPWK#j5u2s9k%(?k@-EyXvyg3ndUo z?%tR}`268#qFb;sgukz;U2xkR9EgIMJ7B-YPYMohi0bUN4Gj$q3MbF9bnSANt;T`r zcm`^J7co7_=Xp=E-^i=k$US%&1`cRG0Ve0%2h>%MoA^5(=;|pe$zuV9gHsd#I zRufmxPxO>SIS)3d8Q6BoJu};nMU&a@axQ(zee?xK4Q=cM<@y+EO!P+cEu<|L(8L?8 za@VKv2o9&>vO~*Qq|F}GIr_+C+WK5hvb1Ba*$)(WvH~vZmh2(i0s;1vGJiwt}sal9FS1wXZ;T(|!wrs4Z8T zerKLAx)hO0A^T4z$HLZp;SnEQQ@L?n+!^@JBiqJZkg&=+(fRl zg7Spsg_w7*uNvwn2Hu0c{p4t-$V#gXdBX5DuQeDw97<>jxDh=DViP-l;Ps=58qsP| z%S*@J8vDMUV=#P^B2CA+U<5+F&o((M`Oa5q2fEzOnoREADJdygwI;t2 z>~gW{)-b2}$sewOh{_WBlaej+@;9z=se(q7UK~numg<~CpY5TYvj!RT0@M`G2>$$x z(qj0@kY-Cnhi(b*?d#)wGkAPP+2JXmP;C>@@cPO1n#`BZbc6=JFBV-gO*+2M$t`j& z<$jaPckFC{H+S3Mu`M{W(9*T5gL`M6PwK%B`btzHVw^eS-^EX2v%-!K_bDyaU?U8JWt#3Pe38GJh%R`hpBTpTn&f-zLZjvWUkLT#6we3?gV z-ufBUD)7)EAGMYrHQJ|Lb5p0Vfz{l!EA903>Y@n&;Z$=)xC2`KW$({PY}P;iv`Ycd zq+Ro;As%a5PJ~>?3B!O?PWFicE+g7EdUl;9&-GFI1@ zuX#B`RdotlH66kTI_Lr#>Cy$KHz+b_T~sGiEs!Zb`A3uEZ%#F3fmJp%FVcj8J#Z7D z3(@}ufyvo6Q%`*(IjNRQjlV~0XET^Q@(`Q?atH)MyBD$`G7oSX^(d40V`q;>`Pl6n z(x1GM!YA<%{G|8YYOK8jlqr`aC23KBWo|6+_%(v=r@_jm*LZERqIG8SOvw)~0PF6w z1DT!Z+M-N05?5Kt=vlSBfq|CTfw21?D(XkTv1IYgo2S@WIgg}2hcfm zaZXOO-gds~Yq&T2Fj<-leK=XvphJIhxKC0wgv9Ae?)+>(?;B`NCMud&2`Mb><4`6m zKWD4q1g}_*GZ1&$Th`ym`-(dPQE^SzX3u^Kzn`3R8VV+Lg3ff;kBJNv73w^NYq=dM zQw+Pu*vM=`Q^MNuO@fC<&QeRbPZ^lE8WY-^PXQtOQL|kkc@i@|PX83n6^mnicC_+n z{zp1EfQ9OO`t)?}h#%ucx8&vNw#AW>Jd)qF>?FrRIbG|_-#|X-S;1FP#+wv@jmZ(v21Y7rH>wM zt5285*nL;@!}dwR4eBiW&+!d2l9bT5D3HX;+%BmBUX=17$HYH|g6FQV+p8F|W3>Ev z)1F2sPK*ZXi;^v!&!^S?QxtEJ!&e`{Ai2|qJPKqRhThcuEu)`e1ZLvr~Y%YH1 zUJB^^s$y3!Ub-aoW@pp$&TW_m4d7%1@B|C^L6QkE&UXQ%q{)W}uL`aN0Dpsvr zapqxr_DHX93?)!yIg2NDDeB>jooxs36LRGf`J+P*)8(i&;QZbfcGa%6FU_4v!~krd zpj9CdWD&H`zps?Na^)XLQ90{ofQ*Ee-BTQ*k2P(Y1Ts`5Y4}IvicX++ucTy0UIs|V zqnC9np^r87TRF-`SR4WStHKBi&i>3Vi7YZZidM5_i-F^Bv^^fli%%9o+^>#eGf|ga zyX%f_Z3a_B?f}$Dqj*mJ9{&?AuyV%Fv*XKBF_}s!uIvLQG=BKWCIAL$1jA6|ovgGR zlNZ|VeH}>DuXjC|&@>4nKc^l&2;lMSlILE*A$SRJc$<}j4R;o zQ^jq}eHRO?GvT6+X!6!0GC)wH*C7-YakSk)aDwe1blQa{3NV9z4}vFox-Hxe^;0Z6_w10VXSR8nlSciGD9TQIXfwBuAr$bK5{sT@H8B4Yn8p`^6* z{kS{a$a(9$?cVRsbwq6rQT|oVV z;2#GglK=d^*$YSc=I`GR@b&I4Iwt~Efpvv!#CE-|lV2O^R*TyQ$D6qO@$b&^L?ub> zQc?idDXPt6F6cwXou)m%@%l-9&#iO7t?smTvhTv)ypZn&xnB2Z0vC`=5H_iE(lYN< zRKz?wF)Iz-U3;G?ra=|C1LTe~!2bzFHuv;D0`-uBowjEbi0&BK>hSS!=S%>rT z;IIHii@gbRtskOgcD;e=)f7bmdIkH_p z<^t3DM^V9J+@frpMe<4IWk^FErhUd_ErB;<_=Vcqf?soi)wG5V6K-7x z+hv=UgAF&of!?>q#?OL6lie&Y7jM3mL$J_XiCf(7I)w?JwbJN(E6D%5lB3w#4f(G{ zG2^JHMoq|7gtYRQtu6oVS7jfznR+ka-rc`e-6O=Irph$FYfP=!tUvy;k&516r4N>4 z+19J}o}}u8G>GXP3aXqh?6Z${D}+}lX$ z5p*_flg#q6OVcd(Nc_98{D6D+Grkl4bRT{F9SU(ogSDW}4Lhb_bjA->9Sum2CJlZEw;*)CYGC|#7-#N0=|{XA zd+g;C)U)4o{)L59veS#ru}iN94VmvmgS$fH=N>$GaFL9=6F7LIdMNrj&AR*C@n+Jj z2;l(`Y7gw)uN}9Q>jYxD?pL7)?i0=7Dr6d%;Ojmp_bEFmCA|o%`e-a_7Zo{ zwg1WIU#e35g|q*nwNBD9LMHv7`Inl-1;PK2%$* zV5BG`6aSvR42%#YNz%+xp#$=~qO*t!ougUq>G{IcqK! z>Fnb;2DNcnc`<5U;*6pQ?hPZ%C2H@bwh0%y?%)W1WS*rqz~{A^eq60)E(ADcfmeML zwi3$2J(ul{uHlyG(3`K1M;U6=sn0&J1q$B)%aWmpKRgi?mBtD}&1P~TFqiTgJNy!> z8n5FxZTK#O+R*+c-gDJHsnP`*6m@|*O3ZoM$W~x1pJ=x#Nq!LjIHN;d84fPKX$f3b zdirgOacaS2++;3JfQl7>i_g4=KUtPSb%TmidUeUd0DtEDq^Q6)Rb z*|jH2v0l*%6_T%CALi}>C(Z(?n-%j=Y;T8oFCvG9!n@z*4E=pSKiQ$1$qFAOs3&u( zZOGi4SQ8%k-UYf()$JAH0I~PPLZq1qJFx|>G;-ur^gfS00~EP&$Ns~yj(c?&zgWe+ z&?)g9W(p}QoTPJaUHvr=dHUZ;$6F@(Eyptje8+0Y=lnG*=b8L^hD3J-etT@2s5gov zgdNV53@N$)KC*z1S#t1tY)>6FMBr}o2898#{!px%dAkC*SEy=Rjc!)Vs${6b80OKg zQll>5d)N*BdjaF`lTA!$If)-$dYN@Ni4P7cJBvT^Fah(q8(*qo)pDu&=dRkO0`IYs zgR9IJ(GPLIb1`XbH_w4IZtKy~Vi#uakto~Vo=d94q$c{FL#zv7aFB+%?bT*Oj@x6T z=tc1>w+L!yaZI8QibYqxSJ#^K+52fH-^J{d4c)f(sK!p_#fN(cc6B(oiHuIu@U6K# z-ifDtTQB4pK=T9K*Z%XOr@^TNgVm1%RWo;SH}b@MjcZRS1uQX4eF@1Y_FrCissG(Xf&2NJX$Lv+KnV_kXY(lyNc zIPR9HVRu~-PN(SHx2Cx4Z#MSz{FsAvS;EMj(y+564W0YyT9n@5zO8pv!4q3@A_8f9 z*79&IP-@-tKvhmhraSDHU(nHZ`}!=ezWW>RmSdNOC+14uc$d=ZF+imhxj|1Mv1IZUQZ+gAsCn&LMIs z(~v8mB?>u90BWV>xQ$gct&_!H?Ft_(Glj-`HHkw?X?a%9tFI9fK4&eC3F8*4=y;O~ z6lMLV`gm~0-!?f6%TaZkcKgju+uZe=-8)x0o{oc&)V)Fj0b5)YW=Kh)jUyoMAMe09 zZ`J4CwIZtqnfK_+Tw}bOsx5J;n>KnCZ2s(IHKoRd*Fs8ZvUT>6A*pM-=4LflgOQ3J zW~aEDn+bYFo;J54IR&a3SRMlTkW`JA|D}dG?jMNMUk>{#!{3)lGO5#-*iw5Zj{I}& zvQWeVhmgJ9Q!jvv$!?RL`!Nnx?M$99Ye=TKl>66mRC8^!n1jQ9}-35P1 z$qNYNW$a?xZoIBUl%)#bL zk$g!qNKWy7U2vNkG4^AY-0^`W{d!8AuV<&M!`Q3B2Y8#NAYOu=>;kc}FR&+$&r?(2Djpznj7ad?)_j(vggYY%&OK(x>H`xH!*X9c9}Pty zN~)EufO%?_3@w+JJ(3?}rzrnA$q^Km6n3|}W*3JTvFTZW;g%RtF<>U`tNmDG$7d9+ z*zx(b4DK_2L8{fJcD_?b?>(~2a0F(L-%%`$<0fA*f7Xus;rc+tZ0H?Vh zaj_6z3-7_L;&WNn#c-g-wv)syS+eCs2gOxa$3Mup;@tMqPXV2Wyi4)H#f*;!9AXN3 zJOZRVGi&`UnDldx1-&0|p>3M#>e3f_75puuFOt+@L+K!Auwvm-;L#cA9qLP0`}OoR z;lY?8eIqXznATUC+4r<{+(zLdb!~+;cJbhF(Qd5MtCgyc0_+kvi z1-zwexLeG=DdAfZb}s;J7)d2&udN1TX?d%-r5YhuGM*W-$ASQdtoLn-{86XTcuh`; zEXQ3*VFl9tURRgxs~#2WLIG8!T$S?;z_!Pw3Oq~Po~?ssUH%VQ?;Y3F(zOk5KtNI8 zR#2&;(xgalp%(>1ZvsjYq=w!*7)3#pB7_=3@6tg;dJ`n{jvz%O5SoBgY4S}x&;5Sq zdEflu`Ejx{vu9?_nl)=(>sr9tTznuLu_OQtKs#_xq-3*rFK)VU%cZu77nlj)g{XXv0jqatUheXg+_hbkr8L67ul?kqp{Tjc1-Un}l7bV<}k=}$hsVEy4r#jAe z)qm2^(W`n;oLkzU1{F*V59<=ski$;VfJf(;2l;6Oh~3H&vkwU7uI0XqjN5T zeq-7dO5J6@-SI5W_vK5Yl$UjPdU<*JnTT4yt@7b1G)M(pcd8kVau=lDm0>t}oE;t6 z(5(19DLm4J=BqKWcCOv~r+0~!JC1W_fl}#$rIA8|%KbNR+;}N@pS``s)JyN80gX}| z&zfl!E|BFm;hIMXK(CloJ?SThcbWE+F4u91tM#wdXxi*?+@X|@2OKH@lJz=!+_)cG zcLm(;rtV`>O%|t3zTlghK8HZtz&EN<0T#zy6p%kb$R#}x9Db2Rz}_5o*OvGB5Awp~ z_}4re{SyCDKegPMTpLgE5CTvj0mmN-&|w#>-@eh|8fiNuKLYUlcNm9GvrsWB_U|}T zd+pwwKe_NnM8qvFGzz-P5dce|$@)#;V2A5Odx1?m(9#7%pdY5lPT_Bk(s#i)R?;rvVzWt$R z9`{*$jXy004}78kahJt}45TwrKy@9QgV5?=T01yKiJr>QXg|jg?$KzKo8gk~7cR;_ z{49plasOidT3!&+eTr)?LG4n1I6)V2r*4Nk!GWK^fQ^ zS|Z~hMEkTEkRv{N1Q*~YP*njqYRoWz-3HBav)vq0hn39%@Jz4^5z-X+ag-4NR7)Ty zd3PP=1^Er0XF<2xUWKMw&jI*V&Lm+OU+4&>nujR@$uZZaXot>^|3jH`bTx9zgS~WC zy;c@@_Wl_)uUOWb=6wR?l7On9b)u8kGDEV5LU}2GsEZ_@@)F)w#JJy2?Jt@S{NWA) z%k%0AGyHFcJk>1pfB5ulPEg(78f?Z$4JO~DggiacAmzRQGVrLf1DN=~EGj=in#(+Z zIMEyf(nK~o(h)5moXI`Fn!}WyL?!@O%u;UQ@i{}xgx(;Wg{37c5hM~1#8Q~3TKxWt zh&MUq%v9}Q-rVk7BDsl^zDv-Ue_NK9yF{IdAVAolK}WFVovp3eE9QWM`-M&t7?UFn z2>L)#toRqKgr+kl2@H!6a@7bR^D?Bt^L!L7>X#<@^YpoCt!RO>i{{&3M$Rwu>VKQ> zivtvfdD4$<`NYr6lF5J*>I+Z8f)*s+uj{DoV;#XsJ`G>q+0?EO3 zDL&K?~BNJ{q@AhF6yQ_m;uQtJQpuy&{~ZYSsgV9@?2!zknY zx;eppF2JiZ+WYbR?1Oh;?W5Y0iIF#Tz@i{Iba6&cVAp*>oWX6+W_ud^AE1VaW6Em@ zsQXRevb+##4V*9)8uL<@`0( zCkRIr&x;X&bSW5NC#aw9_yc2M;ISl(5Nz9<_~T}vP4CGdR0)zHU;@%(o+1!jL*EEX zwczen{tY;}z~s>B_eWYGx0AewF2yQ>F41=2`HCyuqz`V!wmSOM#Bp~K)9A0Imzm! zq<>ZG+j~UsOII52E4CA8DHRC(L*8KB^6J142ReYJM_bu~=ol2I5u+#k0L0EAyN?*g zAx)5DwAvv|)b%#>w+I}lN!k3r3d7ju@3H|L>Wkp}Sy{xRGpiC#unCg)_oi&Rt5L~V zaPG-Zia5V-##k~kh=S&vz(En?o?EDU`q#H?Cq;Q3jr~pmbRTWT#|YxlkdDqEz|Eu} zN1MJ;^YLTCD#rwgeZvH8rkGY~;yYqEp*>9U8bAJ?Dc;IG7Y6=5#sxO%oao4+hi|MbB?3X=ESt#DW3C_r%*L7fPfy3=CP09xh zNqzJc8f^`Q#e&;+N+7SAx~3=YKdr8vL!s|;g3vzF9Wkx%!f{J6m@NcYdRiL?NDk-m zFk7N`>6ilm((jA)FcHC@m_Y=#(7T=>>h!r)9W+K5dP4(d^$yr@#G^J?*sn+6`~ny+ z^`?v__b3Mm&WS|dZ+3(YWtaY`Y~ZlpjP>Mbix=8Pt(_aa*+0(9kwN@-{I3YkL5U=; z0;VyrwVu}0Y0CHcZC7d9fUducVMqWvR^AJf0&Fs33oxDRf>glU?=HJw7yOU~WL9if z5$qFd>S11(xU44&@!w7MDod{m;7)&-=Vff)ar;V>G+WZY2}m9hZ!qbJnl_-ttDr?E zf)(2hOE&rYmid?zWf0F`dw>1D#1DDs5bH-B>k-%(?#@Ry`}OMsNPY)cFohtn!A@mE z!5!@m_+vjidr^(wYcnf_2f(yO9kVd}4enIGfoOsZ(T1k1^BQfWIF+YN;J96s9=p?D z1GG-Kz0C1?)dgVbPA?Cfs#M_^uA2gf-ajXD;kOzWPAb5LMlJ|a2kNeW#QSIFhS*l& z{D0ZK2S{J-tC1!9g{}b-*E&mtpW%2_jGK^=GtR6;UW9Z3kq&AX-Tf zfve>2L7Lm&HM{vM;MN73<~l9@Uaz_==l6ZFz;fEv1Im)Szi%(zfYeFj4WAc7 zTbS+pG=N_K@)Q2_qOJ$lJVG$zsKEqJyv(Y2f4{B0txb`5M2mX-<%I$mi@pb+qhlX` ziPn}dsJS#jkTO~%yP!yF5|(U zGz=iWR~ExL#2IFwcRSzTU_6G=xA6`A1gyjVwBHm|@6qzZ)$KpRM8aBb(7`(Oc#c{Y zral>v#}lqag#^vZyi9Dy4@*a}8t|JvabdU`6-7swo{mQI4SecrxvvKaUx18Kz{K0i z4+?4k^+yk-Fs1VH)_p8d$gB<@_yWt0o`#0MXhe$s*~2%wbktna{yJF4&*NwDe00`@0u z_4-BZ?a4OZ&I^YV0Z2x_^f%Ii?I>lfQfg?1(0Xl4#l}-DLL82eVx9ats~B^!>L~N3 zAV1*73=a`?@^@Dcj4CXFb;}s(*jAL{q$fN60Ff`aXxg$iPk^A>)^?VnIq*jP1q2TF zt`N4JpY_K8V*hy=MC@A3faKq{)!upQ-wjI_W*E6A%l~(03A+PA+s{LP=}_%z&MT`) zOB=DZiwX)@QgC~rc06GBiB8VD7WeShi&meF>6o~2U5W^npSg)I1Eh&+6EZeg3=rSe~4q>SO*M_w6`Zzny4XiEKG~*;MN7NxSce1_vjS# zv);3T*_kg>@w0oG&p0;w-_H0B$6gDZ3jJ`o)|>uGczAb3Y5vPMpD*8dC5IO5V%1i# zyu8CwUDj#ek&BLT{7oIQPH~Po@oSuMmK&WdH+!qzA)B?`7E((|DfGkMEhj}`lmklr z0r4J4)CVkS(a}G5Di0nzZ0;T2Gd{-9nAz-4Ih>~94^DyyNT8a0qH5dWxJBC0YHZGW z#g`jA>R}>Rzfu0$&`g&#<#?2if>R*sa|gn6ro+SZ6wKkwsMkAy;yU zs>3iZ>b&9SS!4r02~5UD1aD(GuTst2j}<`8A|(+Wf!R6lK1x--`-;C8n;HoWpi=jv z2T~aAALmBl*$8*fzyt_7{qZ@QvC{k5$?sonow?3x!u*hr2S^kdHgw;bi3zkz|8kc> zo5)-eA^-DMenzR5_mHqciX#kbAat~mXR$qT!2Iy)uy=dF-0|$NZII?NSk#$>z9l<@ zIdQ~$kqpfQr*9vxit!zsMKMc(y>a@qWGl2cZ1J`j2WMSj2xKmLH2pgvk{>r;pSfAR z^xv+y{&vMa`cg8vk|xWcM)FG(GY_hCQ|kTWhSgBnCt(qSY=m>6RQKgjgX6}8Pco<0 zW6fX-i_G0gKqN%Li1)h)4QerHyCnEkP>xJ+rW=c4$dO~q0gbw=RtUpRzM#^FO@O_) z%)y7q91R9X=rNsR6pKpz&(+!z%_{;ULWgTRFA@}|_SrRe{fKmSxIk*Y*Bb%!!tMw= zS6z}w(A?+Y18A{kdI>m?br@K59Y)yQArS2Gg4GN@JA7 zExW_WDN9WSQ#p+SlqDEZ>~9BG07(_Pd(@QuUUABMPe%Fc5I8cH>u4?P_#BjFZC_=f z9(fKO?1j+UgLy0|4?esknn4Wh>MhZXUvfz}tUyUmaClK~i$ld@dRj6MkJe*UZg73b z-%M`?HnTcEWr55R?Pw%iZ{ax!ImJwT+egkc-?k{tqBxXRej!mcm{9HwM=gfANx?jyp>BFd)x$Wdr_0gQ-op^F+&}BsbnE-d8aBQ?uYM_BT1yuZnfW#fP z4bL23{}$tBmhK>?7gFr}A1E)8Y&6Y-VztxCRx$sQw;qR^mh52;S%avA_qtIshqwOS z#mul9+#+S0^-1jEaaLELQ)|JCNchl!B9h~GM50xxbhw|d19|THRCQ5#Fc1mosAMxm zc}~+l@}tX~*{8-{cFeVlt~_hyMI#3sp8~Z;DL-a!rr6rak|I~~h^p~mSGXk$*eU+J z)|c;)Xxl2H`G+vWGrj0Uyyl!waWx8D?gikO96>YLId+h@bO&We5S^Lz_2QE$Np`I~ zqN{JJxgr0)ydj<*rUgJm+j*g;0xX$3*e1j+CAdNZ7hR9a?ASU5w6&6^R|q8VQzS!3 zf^-aS&4^Nb&laY!^)3I1q`pS+&B3U28nXYH2WB~JG z3bXJfIMPVs|#)d`@JWD}(%$Mu^M$=;2Ra4yeSEPC85Ub=fM>*Tnd+gCe(N z_P<(Q$)W-5;2KbUp}K4*>I=)WKy6FXmKm%?Ss!8c z=h(II0m5ctINrtKTs3P@Fav@{G)TB7a^XExY}u&P5O;?6_ZNSuTBZ`W?xpOHXz>h) z<$FY-OnVb{Pd`{d&L6=ZCI-{tQLl2tFQ?p%3vCS-b3pGNR&tE8W0k5T#q2|jlUYdy zeYhTIX)zfB++TlmNQV?Hhfhth$$dKze6VWYG4-qB7CsiR@m+)d zP7DX^Ld7O0X_PL5V3`L9y1UQS#DQGXNvy0*AF)WYRLr@lun~FTFWq^6 z{K(DsaQWjK%R||@o5ev9(Uh>nua0EU5hzY84i|(!VHifoy?85muf8M7*c8cdf4eSO5N@@*iYQ=c8Rw_@uKrRZR3+P`Z^>KOxGrq_(o+%I z@mcd73MfGhBVt2tekIp8pPDl=A|$oRCE>+egq{3D33sJ8Es<_6{2oZc(m^#sDr87@ zpvoH`TgG*UB6JuX`WPXxPURK{+e=yfPNRWo;yFZDp=EN)g5qVskn-wR7u=7l)BWk} z)aX4=O*!j@;fTqa9~C0wJW0l?irhKOl#CR-*dX?pb1ka8!NdZEv07-_%Z3sw$sZu>duq3G%t7+6j9RFn* z#oFcp3{3Cn0ctvyEmk@}jN?@`+CJgIZ6a5xr|b}_H&3Z3QJOF_qIc_8`FVM1ox2WZ zs^$&0BVB#OY9OSL-H9vl#uG@GkrOCv?*Rsp^ZLYwf z=_q1Ph~v>r>r|+nPeOyKSk!_IwoyaPd4{32Z(DsUwfWJwD6aea2JLy0me?le1Q`l! ztxw}+QIEWc0FgA}2(7g0G;rnv1E+2H01t%4K7N8L%%JNRFIMt(r!%yCow8thh+OV> zYuoQJcJe>=O{kwUxCWcnL->Qh)`{67pv&>}nqb)=mS z5BhQIJLzW)_jZ$*f&Sl3)zfw<$gh3CQKG5orQ;hY9L@%9a4ZBuNmf`r>vjp}Jo&}` zypnftn$?nRljVSs-09oJsX*oNR&+`*h}!BnL{KqFXcJCXwDwxc_L*F@?A}>(9|A(h z*p}OFOclOrQiyuyB-B3?=&v+>NyL*x{;c6WAgTLx+;q@bR>8)u7JH{OGKs#xmb|M$ zORYjnL;2g?CfFerDBn=JYl_y|dBn!m5@|IYLM>a6v_&->xGEBs0t5Gyv4S0jx73h& zC9eDj*eIm0xO{{ocNQJ>SN!LS)7~|Jnj^z1Xf2DN-WVctxD07^f6e5ld4}xKdsa7m zKq;2iPuYK((jx1G_ve6qHE)#OOeO)AgTt0E^b@-S>vN^bHcIiQc<(>PzDj1u>6Hgt zNoCXxTbBZ0{A{B5hm+0Ep242|Y3NKzxWxGeMcd>$-?oR{VoSv(Jqu&Y3Oft3Q*Pq) zzT61RUwiq+Ob*m`@qln>iuJrOoK0}cCXEX!Ht?2z_We0IMv=PZ-pI?kdpj#L8ViAs zZehD)7c~0|TwVo(O)D5J_s4g#F~Eli+0--5#h73m!rT~`SIuYZXZpFVt?j0v9Hwjl z!+$2V_joj2++}wC2=X*a`-LE`!VXR0Xh$*AeOELJe7t{eS#YM8-rTE!B99l$3K7k>=_U%5p(lXYt;K1RUs~HHEr)= zl|?vnufY;<42<7ii1@6R-$^8}607ncm`AWAnvus&vc8@+01RR>)Vd~ddI$hZ$qy%F zA2gi-0pDUmQg%%}Vo8}GGOz7T1qB=g@2uv-V6~oyE4gCt$dgPa&l@d#pOPTmGeBz_ zCTM)V;fIT9>k|q+0G{*UVPyAv>%kYTe&a*gw`?39EC;!E8{XTGtSa=bAgndzNX<)z%Tcg! zVq=KI)cwL_3j$O$YJ6<2)x=MFXZwziSV@XM?EZZJD%J0iG%d9%r$44Lq&!j(IIdK&cvpuG#a)R?xCI(B*LNg8{bo zjqw=+MqL+iBGq}HYahc%{**&)FP3p^Um!@0GkY zW9Ds%{`jI?2P>0~vzoi&#Pz;U`661&Wnd*ADze4674eRmH24l>Q9?9jogaEx^B-W7Y2+b#|FDv2VTL_g)``(~i9XRbbxDsd8aLM$L$qAqo7*c6nOh3w_f8Ada`|D# z;HK<6MX=VUMefQQu_(7iyT39hPr~g0YHZDYn{hIawQ9@PS?^zsdSA?ojbnSLYbW_L zJbwO}A?1FJ?R8;f^$^lOfUAEX>){WVVCR71m`Lw+*NZLwRySag8myB^Tpg;>(%TQ_ zH}XLCKQ||_t4}p7Xjsd&`>RpuCG-fuq;B|mSV8CnnpPrYq?*|QI0ZsrHrtX;13%pd z;f9P)QJbDm*MDlLrFn?1l39!^ZSYoi*_%Tdb7l|CnTN~cJHGI~AfDO%9zuO>gRfr5 zD|02Yr?Ti#>~r{Sb>km*J46l-)QI7NGpPuBa&xB2B)#jhG!B*XI@gXth(g4up=&ey zT}R*NqliO$L8svU+m3(gyChUBNujlv2E`wfBE2bV(uaeYO0UM&+5`Rc7b=^&WOfIY zs5PcNZoyilNkq~FsVGJ_oPDD(c;_YTLK-VRo-o~ZdQbanJ@A;XWG(MOZ6w+aUXKQx z@Ams0EXX4_Nq1NRECkXr9zA_2;&a}?j~psC8V_cN?_+1IPcm)R_wCa>z{b%_WbOCT z3MuIvsb6d7z(6fC2zl!ncM5xsH(b-Cb&J5})8sPAWgDr?w)eh<-;ZVL4d(;mf)}Nn zD?6!|=&;y}_1rTdGRLg8;kIvQM@)oXSJ(-zCmpIQeDEW{bc=S4$WAd;d_s8?j*(kT z4`R^rV6JJRHOryHR2L4sqD3s$zRNn|lVz;z+yRHn0GU%nDj9IBG$tn!jPw}MWsEet z0a1$``$sAlUlHD&p83GBpYDe3u!SR!nHIa)#X0e?U%T^h1Cd>#&z6#H$^5nu1uP!s z9aGlDAo4*J@743v#=F{&;4NWS2+N`C<$C3S`cc-fzW&VTFrsoR@#Qev1~M&2hd~P^ zf2H1FfX@b6vpkp?DtJ{130x4u_d3K<2ntPTJ&$bYI@)EYPTWIcS`sz59;HmZX7CeZ zdzONEoYLo8JLmIr%In(?B|2aRW?MvEYWdJ@bZe)`@#ZDS=V>rZ5^WytSfo#*df3mN8P`@@ z&EcC8F{bJMHHn-huw0&{VQDX=``g+4>0+eIWE~xA8jEy_oyya_n#P5ni)wEtGCS>& zVvWCMOWqFT`A|OXu$A?03fy~j{bEb%sGrADPxZXxs8HFT{>kGB8Jq5Kd#;azfggjh zZ%_L3pOsCYJ!g5`>(C(biE4z2Ro5zu$jfrI>ejX&sJ~;eB`YT<_wfgo_VQ9)?rXk- zplz#d%Mq7cCf{$RW{r;_y9rmSlfzoup104AUJ;wd(sT*gh-$rBVkiG&I@cVqwZjs$ zP7m8kn0`HWz}LAtceThxiNyXH8~LC8NuNm3=9o>rRkDFJ8DpbV^n$us)s3e^*7U|U z5y{VN{D!Ivt23u>8u)yXnrp0Yun=sHIa-j@F&;-JA77hD_x=;2S#p0@r{v4oYT^^7 z(FXKDHS5l*57pAxfx45^q`p{&jgHqu^l8`L+!K@EBkudQ&q@Vua78?5Pcc2883Yei zpX%4usNW=?Gv4@ioaXNIOD_DnaZONw-@dr4^zZ4)jCdM3qsDm}CS7&oCq8QXVwa2b zOcL|--o`Pa*fQ}HLBG<{{}d{iy!Ht`BU$trI59ZPj{atsi%9LT`7gLDDi$1S@yC z)9gBNCiCiA_Fhl^v&NGaIjK*dUV09EKcH1gJ)N}FtG{LAE{k|dmp{Yp|R;}7&|bSCg-^$5Mg2!`g)F(c-^^6=Uxr`nm15Ynej=_kKhlelINqgj$x_f|{0 z8~it$CG^csk0vP7G$xC`uQy|w{PyC+GMXG*Nfz4{gVsK7-f=Mv%($?%fKpfjJX@m9 z3U8xs$VkQ#cOneQ>%D%&bd1r?S!Kk}pohzo^dO`9$NG$ie0fHEn>_3Z_C)%PRN-UB z%?sbeKd|b@-iI@dor}c{bxw9f$YS}l5uQxJVvRd2o8!o-@^xCH6fM>X*7~~>MluLp z0V?&T^{+-3Wv{TB9Uo0lUqkUdz>qk+ds@(-yRMz0vo;OWuyxq$&lJ>SJ8K+n@b@jn z!0L%-hp=8J*=2v8S?5i6$yr%&xmiB;`CS6LF_-X~n|Q4pgT3>}Q%Wk_K1-7Z_;{xQHo2fPh&$0lL(d(jVW9Jal>7;;dzGW&_W23`HG z!T2J5WJHfvn)IVj7B+Vb3}LVIo~c%m|!g-*SrWJGiW6Z^OCd1i$DosOg5-C|c`yk@Um6mqY`3%^k zj4JBuYK~v-2A`nxR>_T^-68k=^~tu*QRe>DLOM7bLHAA3GBoZdX%uCEEeBsY?Q~ex+YKBAR|LO(dcj!xmncW=uWwSw!Pt zsjsw>_}V?r3le{Qz1Tt5gYQiaTXgYjHm&M+5WCx#`Q%`AB2J?$c<;xWsP!E6CeJgk zy`m7}wEEF zwN~Dcw{HC8?5(0y0v>Ho$nd z)rC>liuDqYHprhUIq(Q%av|T6j2<>HEWjl$MwXyw>$Iihi_H3fw=Sli&{^&7O z4j3W11{h^q`3VmQ*7%dAv>`2j62>OE>xVHR$RNg9$ zx4HlE&$;GjgS~C5i&hwd#+}gGjU{nW882$yUo5*+s+2ka!>;W+=E9e1hpoU;7R zTx-tfJL!-+Riw`!y|lF3TfeyaDCMzMaLs_IU-mp*sdKrEO7x9AjHyDW`jgY`iy?K- z8AUaodW}?Gt8sM;cD?O{lVnpKY*?)Dk99cVc$B6jEOpkpH|G&8ar(h8S*iInV9qN@ z?`v7(R(xS^qb7o6wJPL-UzD3BvCOL%BDMfXvPd}GTLzKpbyXwpu(PW&KKm;Qj$iA3 zej~4Vn)^MiqZm|KL*%)H<8+R<)!p6;1?`4EIa(C*n2S<~=wh4Z5c}!7-y7ERHnG|D zi;)Z*vqg;Jzp$PE?9cC;193Ae+a{`Q*D?@GJu^B$%!N;y&koLnzZPVa-d8_vv!5w3 zBcb;+Of}4h%N|DdT(8%&`+i6?SXyUyL>~PZZ-g+db z*LbYBL}PnK>+FQzWZ-$es{vJmpPIwvW`U%hM`cY?)$Sww16kA8Ygu9goZe%37NP+% zg5lYRo9VR!jX|)M`!nCW?Puhd#wG<6i=DOXPlUYtK5^|amMTe2y&0S4oja?N3n&i6 z|B(45+UQY&Ws;n@EpL!&jwb? zG!vdCi# zUz$tHDG~X);$1)44IC=hLQ9ugjypw{LwEZd3w%nwf(jUY?>`spx;a`HAz;SktyXb; z!NToDQ+E~nD|0BKGgoZ}h9HBGkum&_V`;3R@%Fk~P`N?!Gpo}p8yq`c3mVJOj=Io7 zY+WJ2wZ6=_zy*ER1R)ABw6;ZHWlPpqr<2$Ry4ot~Iq>c(Qu7bMR~7Nxepu7-i2;xJ&mmzzDR5l=8@e}p$sA~|I<{34Ot z>Id!|45=%xBJzc1$)Ha(lm!u~9Qt&r7)`sZiDI^x?Mo>2r&GCy`o&~)jS;t`pAbPD zDNo0ZK^6B(sQ6)8{!$mz;?FkGxjr|w)fjSt=9I%`|}B{In%qp~``4~O&* z$7>_`Gn=Q+L~g+bKQks7jLSjDv0vkgyks-WOR|TKt%O6-FSb{ z2y-l4Jq6>x>iGouORa782Pf2mgUf0PuKl#e6iK|=QzfZt(3gYB&_iGEh|=Pw@E`ax z<|_Z7IrQxmw~d-y)M-F~26D(HitmJQl}ymj)=i`w^PfRKy-3%0oeeG)AAE; z@l)0MJ1U3x2L0_~hRerX1BQKTP>XV5?sA#`Pp6X$p9x`?j0@pXphU0uzBxA=zk`Q7 zWL_eYG5kio?S}7nqKmz*LLTzkxPF^&db##;L;?OpHX=OT`#CY z1}V3qZO=X}wYUS~$DwzcmX0r4L|kK~fhaI&BQ%_T5K8C}IddRpo%YUMnh1mAjxP>U zfma>m^-~eMo$sF}8&p>=si|OncRMI1@iq`kq)s_e@H4B(#rZH6v2@5e*0voW6QAd? zQCXk1PRXGj(1=3imqF{M-m!)m#NsA(o^X$^=LXq&*@got2GgWNL*UuzDS2h2Y_|NT3HN;+*Rzr&SWXV{&kmPj8k+;zr_O=G;3lh$AG;nKsAYLJ37Vn9DQ6nUpx*XVoXQ!Jh2B; zpoaA(OKMK(Rfvuq%#0>k9Vp}eaar9Z=e+K(KAiBQ{I~1pFAmCR`Vt|R*!QQ1(xu?R z{nLI6x>jDet{CF>*!0-5C2!!~MY>@n2%LcS(H#0{ztJa%DPT&)C7zqoaGPeO zfj&e*9|jax+;8W==|{L}>e+5+0@E^yisPqeI2g=^I|U;oGJuVk*6g%_-FiOspyHv~ ze;&=n@Gm-xyn>*GxIp<;p$Isq%F@be2XgPhPMt=ST@R2TKj6WQfGxiqy?7)G)*_E6 zUQ9s^YDM zK7z6iSfV^-3NAK=Jmb6ed(&BzqTx7tl0hd;3lij#HO=eK=C5&1fh?WbOP#=t!v@ZHSUd>T(J)z6zjmMbQKZOCinLPqAO^vFIp8(nir?)zZhKv zJ1&E3vR7Q=))LQ*HqZH%~l(@2JK`CdR|V2 zcZs2V910rfH;RE$O-&i+Vc2eDH*(i~7C7n?sE z*sYACcZ#|MiR7j=75*uB^k_nJr!17o9J5^M% z68gC7rGj`p`dZQqL8jLCQlk}_my@UL#AT|jRj?amr5~#lusfte?k93+1ZElx%T7}% zx5!>EiRQv1zE@G#^uHF&lled#Ptw+|0Z^%5cq_v~-jhzFWTkYNN8TXS!putkVVur( zFTh`AUG|a=Wz-rQgf!Gl`v_|BBm01kL+YaR*VpK`GJe}uM37x9nSP$42C>wmcOPMU zabsOsZ)h-&%0THVge!TH;-lZa5?3I*1dh=agR(N1=kcnqVZY}Q+m}MnNsn;*QUD}x zERq!!+NqauadpWv4exoslYX_ysEjEBW#Whia1AurJB6_*Qq;Ne(ff=T|L(^Ut;yA@ zy3$f--%eS~{c~HyECF}_BeZtD-VCY9EoC3z5R}D+OI1I2!#++2%4RTc>NVOgLNaFz zJAE^Zyy2&l%ZM+9r*fLccC%-=lBN;Q96gkA5m+EzB3?ru>KwAM%xgMY=gZAIQgf2{ z-~Np(tt1Gx0v^9e9-<5j(3RQ@>Ga-unnf&^pgQ2J)+^e$mqdI%oTT#> zs$PX9XtXra(Z~s782H5V3S8_Ry~FhgwE|=+od}|MWAy- z_D+t2`FeOKir#ke9VN8PKv}S>NH*^^(WpLZ-ne~QQS_e$14g_|*^x?!6x1#h4Psk0 z_c`NutS`IY>F6B| zehds^i1^(fgoh$iw?<$zm-f`iV+Cv7@XcvMxQ=7) z{{1#cK5=n(-x00DsJn*9MBI5$dh0A`!m2p^S*d>;0&N$j1sgj@q|SQyr0)GVB?r}$ zI?qCZnNaFtZ4glLoGhZ))lSW@E)QNF%&c^oYa-@>Uy(scVLhjWP8$3H@~=BhCV*mu zJ$&8d38@}54WIcB`%Yqe81;21gWk(5%?Kj_5@z+5Lup&MJCknIe$n0Hd@^?zydC%b zHa}-*={$q>O7&u70b-rO zi#QP*7h2vRk~VzR&&)b(6aYgMbm6yVabTALW7-=7^H8>sg+z*%!aslCpA>NR=lZ=C zHSa)V8s8>OxEjQcp|m$eHZ)o7FAYQ+2phh47~E@gp?tIFu|`74BfwB?pq_8Jn&GJ!M&|;Jlbh>es^b_CP(d3Mu)3qZTvof)!>>l=tr2!PQ>KjStstY*gzDd_$ zvL>CH8`hRU%CF8+~+510v}{ zg`plxV406r8mCZ8@@W^V`m0Q3f>StY6GWqMmi8ggCoVN%Ob1? zkHt2-dAuGK-kJeU+V8e@#!cy7N~Mn%FxUG4CH(YWVgnB(;3SgK#&C&W9|V3HmOrZF zglhY9`uPrb6*Lt}B6s|h$M9Dd7Qnk)AA1$q^~3g>f-pzdfY>Mza)@!ZtMr<$U953q zZ@#d%n=Z3YWy8Gjt+6TGj@kkul~X?b(m)JNy3hJo5MzM^axtLTfZl3l6cbG7Hw^Z^ zS8z>QPl?;9NVNYjYP?Qijdmg42(#qrzzDT`7(r63>1#2l4-3(Jn2NBEsbdrgX^nlX zkFp)vqS{P*$cTrLw6I|n?aa8uqIrYINr!ktZ5+)NXd&hNB`Svo8Q1Sfqk3Wwhw&n@ z!2EK~10%Ffs@HxlG_bzVNp7Glvi*vKIV|!si5{? z-ixBIAiJu(TszHwl?%L+eA#rB*V?!PDP8hZ1R4|-qDEWmk{5|%^mQ>c{(c`#KDjEi z-hlLquFZ@e907e`oO~@=e6-^`t&P>Mp8%Q`()=xk7U`yoa|;>Kq$fWtWpsZs7NAab zaooi>%7jrb9}$JI?I1+-J${@xf*DhP3s}*pJ6(dx!5w->HVH~Lw9!nq)mw@i`E;n0 zszNm&*vDUQ`@4nN50o06Tfv#^G+qlJLf)tYK*`R6Yw^~V2Ga zuUg%Ym0)m|i2o01#y}+<+D-B9rrjO_ zWrpf9qVL~8gMhociC1=4lStr1-bkH7_VK089)if#%bp#A_k0?(^|7Y0?-Lb=iPJsi zG?npMn^R-6WS^+xNimaWwSi{z?K@b>FTM43ec1R-GxvgO=W_mE9-^UZWDdfjkbE>k z{uRd&{CTjfmbr@G;u#$6pRcRZb`TGe?|D|!`lEm2b?W`Xd>jqkM6aRqLPsB;=x>TQZsFQ!E34@#>r@s!SWI286oup&#;iBu# zvZ}~vg^AOH5=Pto66bO;%v1v?<#3HbJ5$5!;Qc%YKPUC9He>7RXTD8&sZ|edQlULR zQLI!N`*Sp2E%rXRX%qs~w7lVHgN0K<5v8YTf}dC4yv*LH-qNJ!l&kP6+w>EQ9ZJQAu|8RqOD!Z92yQZ<2u#n`*_;o-wqL>N4M1z6O zGna2$lFBj{B>W6gj)CB=&a|R8Iqp@>(qqgH9*iv>TyY9^? zKfyMr+C0%#FKq~ddq6KkgN8+^VfalR(NXJQnG=thz51oSbz;r*G3Cv9!@GHpgSASQ zZZP&+k428+K7zLwTLQ6tYv6m#r3SH^$Kc9u0A)CYQkT;Om-saP@IEs&t)i370Toy( z29XpaPkr0OOiTU@9wa1w7$6N8(qxO1%Wqo*@C%vOE9hONhoERMpHS)NCP4 zvpkK+-=_Zgp2mUj%KRYp2&_FMulK*d^Y;F@+W~4e{Qcek{*J3J-}s{=SCAXPMG@X? zFwXGmKTU#v=`v+gAHW;|Zk12~Y!DN4ydN)%^|=JT%HTS1oT=Lb`L+M^L0+!){q+>2 z3ULKhf%8fTjLS}QuF2tGHo%4OkX=``A?<%3hRoYRs723CQ&aAz-cI2XNDhQD5Cq)z z|9t2Q0i1JK1+X!_S^`-MS^xzBcm+UWEde6=KObaFUErbnN;b2UcN`&Qpp&07eZORW zg1~bIs1N=%1p>N3PV$jWsoj$a;EalITGBD?T97k3j3i9#-%rbfy;>b-Z*T9dr-4VlM=Ky<_U{1EWPe}r?=#A) zfa5cF;K*Cj76qO`01cc;_(B96?SCI2{KM)6N}RM7l@X@mqvND5U88JTFoPyYE&}(m zjDHUlq+0O@#V?HzkfZ{SgJe!cI4KSefddvinEmeu>p_CTN)@nvBuKqJrv+|fhgAUD ziSSs~zaLvW4v$30%6KkoTZjw~rqE~~F9Q}{!gCpv|IfOrk}Ton&JKrtfRkn;NO(gz z_t!3nA~Z_t?1MpC1Mkb=T7xxqvKYS{>+Qe9W|1C}#cWI=Hs zuvxk@mBLh?ES|IA{JSHZ{=HU45=48U1Z+8w%(hirmFj|XIuMv*LbD&>|5^-7x5e(O zoDxmEl=1nRsoaE3G5tukh%-H~@_@2_gFrfv;q-3_P03I5e_w6|_T(Z^qG;{So9TTt zTGB(uOCzAZK_aMh22wFW8r94{M>Ch z%s#;S**zsZIOW~iH&ou?*ZVr3CzjY7_Iw0&GBp*;+|1=B85{wqljbdJQ_Yov8ibYP= zcdzb+OFwzkW6MyHgzGYEXdXp~r-xIYtpumqpJsVS6HH!DRt6 zYZT%+zjpJ3SMx;>Aa@T+bsqq!ie>*tWk7UQB8E#Z2O6p*5$e(&OTC}dqLLj_I|&=h z%C(z^N6L5W`*#MyPd&SU+wkwZ68;fc0^q7e@OH1NL7Fe42v-^asqS!E_xJak$zdP1 z|4PlR@cr*ju>6&2ARhD!^S26k@5e=G3M`&U{Ljl+)is=#2>l0t#pDIH6bGz zLPq_sMhF`cu!~&WeTdc?0Ts}|;h-~NrUU-_&nLqIP(m+$4yPeKS_U4%F+w6ErzchqU z_DoVFGud&3lu5x?N)oEg_5F9P zZOCe%XsOYI)T5GWTTJFgAA=d+=l5n2;v-e1Cin>?-5|EV?r8gy_ph?}y4`;BIM={~ z#juA{Qq@~*u7acR`NaZ-7bX!dN|8G^UR;|fwerqwURXJsTO}7ccWNM=r4#&?w?bK5 z$pRjN{+zcG?#DJ##F$(7yf2^W{Pkz&mUq9jFuE5S3`>EhnipJz3Rl3JHwbT zrKoPKJgTdzzXT}|-<|P@4)NOcsGfUP-j6QbuJ*mAcjQ`J3H~RZp#La@@jFFn|AtzD z!s743PyH@@t|?*uo(u);iM)Gm~gq#9KRUtL9wuqsoSZwZ!NYLG)p31UZWNWgj72 z0L@ig=_|VNY#9yveC^>-<;!Ppxw_ zFFd@YwHLs?zb4ytPW3hynVXivp&NYJ{aQ_w&tgWeEZRI zl*zU>os}T+sS;)@TWi zqH@uPOK3VoskGh^o?I|Cn$L^`A2qYQTg=YmdNi^}B0Gy^$o<8kSBQE>!~G2y#~tHM z5#*}xM3AtZu@GQ$@Mh%3&5HN#l-hxKv^A@|z=5&ZDN)$^i#k5Nvb5HPA*ywMI~e%= z0(N0_ubrKJKiO%WB!sox*Be94`AOYqalH;i3{v#xI_O39CF*mdV;MR0$~e&((n#Q8 zM{h{2ZIWh~Keg>MFx|VDrw_HD|Hi<}X{U1-=3H~F%x)Zf>|nq(O^G;Q@e$lV-H=Dv zr^$E(et;7n+MC(f4)O^~HzZwg9oa4`AihR2=e8{;D>R{ITu@o-?2J*wT!z#0veXs3e4TfKuL@={k zNmwd*Zd6c3zuqvR&P^(l)^jzDWVnu7z9Qlzp*M@yoqZ(28@29jmU1-3^p=YO@o2Pz zMX}dueON)UL}a!qXiY{Om5ZTGo~1#31EIr-_h#ry(~vXw;kx3DXRr$o&$KN_C-r!7 z>xsL*LNi`{LjnEmX4+YFfz*MYY|72#m`Yid9joENDb8c=!_3ki;@u1F)X!{5z zRd%t|0yr_IwhD-L3T7?K9#wg)!_-tZVzfNTtV8 z-Ufv(ZEkrl_j)JGtbFs-4>dr?!EmrV~o38hOg^T z<<{;jyRQZZEz0O{xbUQeNZ|_cy~*aa@RK65YB&LYGtdD8)pC*cMpj#EqKx}$c`hae zO+}U2cd+4Ys^fw7W-#n@v1E`ZKccM1Qz_47WP{PYs_vQ@uNetCqyJ2sBX(hiA_%wm zqrJM4t#H^2I}1y=^YyWKLF>~&H}QROM9IXDdwDKH8~DZ^#4Cg0&%0-syB@P#FMVGS zLm6$}vM^I)>)t5>r+{Xjgx-LA%wl)-IC1tjZ~Ihf%r2Dwk_>#r^&b*qXD~n*hL0OK zclM?l-LdZMt=+8{)g`IztFGi19ia6XL*Wa1>1;z&jW|_z zX<^Ox^FT|RK>(S!)h(=mU~9-UI=#c{*5n53E);OOZ_=g~kKAhlf>w|(n#9WcM(WYj zs@ahwuGzwjUr&5xgMEmV3IqbHSluGmd)~JaF;laXWzN{VfXG7yb>=3;`;P|&ZhH;{ zp|^I1G&|EI(SmzJEej8OB^CH<@!1ibnLdv(XU4A8X{o$ef2;lQNx1+q!x#2u1qw5LO-Z!UvUae6&u^`^urqzR4?)m{ zTDEE^T!T9UFA~0ZwXp4UaizOTv7dvP>0~Jwu;zu{IEsOur+v#lhdL@q{~C4==TCaK zN@{o7kX2?1F;1^bpY}HP_TJtn5e8`3<6Dd3#Qj@tVYTKPB zi|<8^9?2a{lMB)#N~>GFg>0#FS;VW&WPD(zReF$s|MuB;I#VqvFjF=0XbXP?D|ZKh zlxiU-+JWxU#P9aW3X1R!j5)n1^zxkL*k%tp5SQsRTGJoh?bt96TE%rFtqdC#z}Ho5 z4(Fp;*1lW{EA|zzalfHvj!p@v?5;ksqh>tfbsz=bEerH_Lw4siFFj;&_f{7DX)uk8ow_nb8xM=^5nCV&(2k_*8)wT43vBGEOTz%CB3ql zZwyCEnFayl12B;t7Sy*#N90HN=x=dlhZMz?ilTZ0zFT&%L)D7S@6im1G9lXBlI%?q zc3-1Bmj<^yu|gM(3Hhi#bHjaK^*k5F{vZ$bspdEa`ogBwpdHQ^*rgX?w$J*8UuVWi z$JefVGovWHYi z)@CP!zV+5t<*{NvH7Lpf93T4)04ii&-yyxEakQxs?+Tw}vC;0$EKo}t6=A%d(JCm) zCgS8{cF-4Zy=8~Q-%s}iMhD#Tur7Ac$tx}K$tGB%O0!-RzVYKpxP2MQL~=kAfcD%a%VXuw|F-(Xsb|^xXi-G zOa@is`Ty^EU)}&)V~`W<_E8aiZBCI}B?ZV)WU-5%MwQ~qnLT5Dpg|-2| zFAMpA2^!=TK#pPq;G^ml??PT7Wy!5g?!XD|Q@{6Qo9!}yji?5M5dH6+M&KTZ1^G{J zmw%Np{Ky96C?3Eq(*QDpz$N3gO!wagGP524QXRnAO2Vzz=r{cV1AL;NmxinKA=U(< z?!RiiFeTF4cHTb^?6b4#9YOtF+!h=ViBNC&t^5hQWcEXD3;+k5qR4T(0k$V0F`igT z@K}BDAAm!qIYJW?`SILaOK<=&Q?idA=wE09U9a}1=}+YV%a6+5G-lS&NS4q75hId2 zfB}ps*w~%`(12e7B494^Zacn}uz9)%`~rkOf`4wxI^0^OiXt@I3BbGm`+IPp3g?0X zm5hk}EfiYyp9dEIc!21mNcxPfZd7&*IO($h+P45rWduU;K<#$_C^&c;?9NC~+5X#0 zJP=gjWzMXI@j=jN{BCXdpO5~!i*qW#RJ`h$KWf@d=OC}<>8P$U40%XpP&xPtl^zp6uo;rGb}%db`S~yv znPLx>BJ^D(ZCjE04LE;JK*;4G_X#44nq6tvYcEd`%GKjsZl!eG-s6!tX}$6R(LaJK_Ega2Hn&?xBqBsw(|HpQz}4O z!Rr~+TCG@y8Cbo$s{kTk*8T56P#I=` z8_p$*wVl~CRP{qr8of`luQTHX+riFNaIbXLt* z%fTD!{Ph@N-et-=tD+VmRH!cFm}pcsfI3)2GH2}m_bI~974_p+g-U@iI#uYW4S)wv zDMvF0WmHbiUr!AI&u%wMDTg-zrb0$BPodS<;E&Mxs~H9WuAIvUKIR5q$Lk+swEnsY zR_U2D;_oxhm8M2Qqx0|8eFxy>L-)alAxLg|qUA*hI9KkEYxnl?tM$%++%OhEf>g{} zRO}$L27>nfz0#<_*M}6DH9#hbE&x#PM*X+Z2M^s81S`uR1Ng-MeFORh1KL#244j}K z@Hh^jF;o@$W5^s%26OuYii#Yyc#GKg(Rr z=ii5rP(gqb;Y#2FpKraCm7XdaR10MpKvpqyN4087;XhGW?*#xAiwFPUgJzJs=J)&m z{L2*vDszfe$}3wMP;2~EPYY_$UG`rAtosdsJJ%)X{_2>t4p4}Q{l5t6pKT9T2ja9_ zNbRqFzB%9kKKGdGKb##@f$){jm(;Q$*!HiAnSy>#Qv^PQX^3Cx?^|GgrP+{BvNBfx z_4i8o<5zEzK$+9wf6uIwpsS=NcGf3tI^y{MY9j#^ma+oVzMBA&{rkOcF(7N$AodK8 zvHHi5^!fMd=7Mf{LLnjGdw<`ehi(CA5X?{rK>qg%{sQ`PE)A?|h?L{+cTPd?Y(YdV z|2__W2^NFi$OZy7{{PI$Owf~aMUYa3!bSdki%0~(;x{8Q@S6}Z&|h~TFG#mL=ruR8 zzk0eG#J3W$#m`_M`i;Nd5K#mR+CAns|Q=V|_VjcESZnV}fb%*DzWngUP6x z0J;}~o&UXp-9ej$ECD?EpPLT4*$O(TKJ@1&8f``1V=}kGHbV8LDhi{{NHonXRtdE0*u8XiVy?}%(Swl zp^6e#X&-`&5i*i=|5vI&FIj+!6I;J+g*+iZ%wk?4kW3U=9e)iG)J^uP>bFr2*$%h+ zo>sa8?f!ojmqjo5P*g`wcIi1$*^)p&g7o(~1~IpE_G%D5?YBAiy>XY2c=1QIi6Vh{ zmA!CB7A2JgA>=BL&6 z1D9GB9OCTW>>c|DvV|a2fXrrN9S6Df9)%bTQ-E#H({saPQeF|@KhMq;DC~jz;1e7J zL!eJf_BUp^=y^)=` z407E60LjO-l_1?z2c(;hG@#7`##=yAYV=#z+Rd#V8xYAa2v(JM>7roBWc8A zbqx2yPS)sfj8lUF@z&rVI&cFSSBHODC%$6}FvbG*z1JJtt2IGuB?iN(02Z!!fsewS z2?W^g^?$HeVV~R6*rR5_!;P_80t)8z;s{@Jom~Kl-?<)UI}LKFh7$CV=8=6My~p)G zDD(&qvY|)u8RqA5jn{@>IGwu@(SI@Zu)-YRx~~jm_TI}%KkaLO{Gx9e;O&+GGnBf| zFCX}x4d84Y3`a#KJEc_5`uGjZ#4i9$bh01xLLhb_4(#v4%c^~gV7R5kQEKp|MWHGQ zq)J|wx2AY^Ds%32TcdKHyLm2mqxvc+pPlm{l^;%5B<`8XZnVPSaWs6*KXA9@B$>kQ6|vYuhSEJb!3BCwzyKJ zso2kG@f6s^_MX@t?0)m?4jE?2?WTD~cN*oxNho`=+vK}9v?~%xHv`6ln;o>`^2`v+ zAV9h0#Sj$_BE#3=OK!0gb#4l6Pc`3#zQ!bgBkbjed)j%22uj#wIXfC@`K~LHP&{nL8@(6$n?)0B`e9-0n z)l(UCjdot`wZhb&cq-^yT)p9OyHx-yp4wlit`z%*sg8V2ox8X@6yPzIpIuyO7BO|y zyf6ch?Y4qnZ)lDpaA0;yJvN$*Uwp;tGyLlH%q4n&_R8Rz)4r$z%tUr~OtvQVID2QV ztcBTdxZJ_P1U9FObLD!Wi(>xz3zHZ7E&v9GGWN}*!N4b?ZBm}vUZ($`4siI$L~2fZ zvsR^c*WQ2d^>Jtv*`A?Bw_mQZ?kD?S^gfPZ%+30AO!QdoN79euR;+U|;x5B}GDUY* zp4}zA3Umi>xINZleo0cnF8rf!=SOuVuaRiNs~3Gyxh!72>)HiP8%|9-HNN)mW0^}< zz1f)ct>tRiPX%F3-nDsX^xA1VchYkLpti(8Vpi_@LPE|6O~rYDKzhiY86{sw!~^0N z^#TR{uDp{D^sfi6C3j>GC_vLKxo1r3$C(oMChLL4Aevm3GVhuG)mnh-G3PoZ7X-cc z<IJ+btrX(c`UMKu@Yy~~fq~R%bmo+}$%`34 zD&X(xCqUs*XyrYrXY|6r;bogopWUlsrz*1&2Mm9y&4j{@@VcW4?w+wXMg~A^u!*BM zrIVO(&b*t7)eZ0hchObm{+YoTgPLU9s)tj1vaIaFg=X`+LT0>x@w+cdMaH~_J+mUh z{VIzl5&)i+ix73W#-IN z{5;uWgO4&{L1)h7Zyr1X0ISt-@M3p4Gw@odw!gsSW%zE9i#uNdz~m&ag6CYN*>bAA zS?y6rk`ZXYL3@@-6CdIV0^{5)shHH?1WWZA{y zK`Dnlfujc0HLILVhh3|!e;ldb?!PH^=t%m;0zs{T>nhVfgZ9QZ>R?BsD#&F33_+^j zO@WyOi+C9FxPKyd<&($!eDqs1d?``*F6>}lBOX_GbO2cxVa5=}6nw>#+BEmA+&y*r zPIoMm*Fl%yp)!7Epw7%*&uVM2?133vETAooXbN}-d8Yx`n%AAzFt(kVk@EC#!Fcp4 z@-RO-_Gs6ifgGireAqwXX^%BNYPGm1A^W)4k9ohZ?C=wOT=qx{zv#O63qpFgCichc z-jdgCr#Fl`sL${vR&8pDnO691InF;n+A=@dvoH%-?f4B%wk*(}@n8~cTR5uOkyg&@ z@9hCFrmGo9aF#4gJ@u?zzjia@lg+x+CB36){E;?nYq5?;uWXDQI7w4gT;aZE2c30( z6Pdo=$Vohi7*N&`eyEl=;wPggYbjTF@~7q;NMF}I95fy^stufW^j+c95Hw3V(zfp( zs|wPPk<2>Ns)`Jli3zhEX3aG(WfVLFZkoF@XM(V7uI8O}F20g_M^2J!^!NEM-l(-R zOIZ}c<+1~vw$_qvaKK>Y?G$e9t?UZCZ+y{xN6zjtbC|lVUqBzx14pWHgwj-)|U^IG{-eOFIc@T z1_zH$i3UFFLmob=GqoR?j-Tx-G&bQ?tKNw|nh^(`4!@hX652aiw;@_+6xlZ_mY|CD)B4|sOPYm$#29kf(0inK2He*tnfUuX-&%$PyjgYt2}a}z}$1~TjpXZ$S2FDFQGB-3ZxI!c}zsrhVW#i-epA}i(TMlTKgpT!C{EVP+`qStp zB#sDO^Kj%Z8?5&A{OcB!IalBOl{6b@Yo4J8|NpMkA{H3VG=%7k|CIy%3|uQ=BpD12}l$Q6PR}I_c03N^tI`0A@y2U}51VYj#jo zk{|L;&C%lM25euIh-igxU6MK?qHStxwK%SLSJ~ZfXVpry-ixUVf2NIGl0AX7X|e&4 zloMo(I=CTy*b!j3`h9;mfb13to((+LHRzANLj*I-`s-+pNBl!P$I0B zNkDbM!}_O0@4FU*oSW(N+>{J;19c6bnFX$VT*X{G67Z#9knO+t0nkaEc^)lNPSj1e zMVlyfyHG6)Q}9wIE*c#{9`1bUSdo+m%&7yPKqbtxF|6*wIl5e}(Gy_mc^#lbEc>bc z0{8^5rKK2&T1_);!gfnrZy=_M6`a*gVf5K*YrbfeeG;g0!86cIe$&#z0s^oL-;(`E z+4uL~3eB8uAuYAcr@wL~(C!Td1OIkLvCR)aag{UG;9^=iN>=0T-;2MhhX)U>xh@*R#P=z+sFJgbs5hSzR(MR4`esCydOo3E^o zC#$ZMMlJ$x#C&bYxsSyKT^W%aE&^S=EL;{gL2XAyjJ13Zpm+}FVUSm z%`Mz_F>I;7&@xJ3&ieaA_8Eo?Hr1LeT3DBTA`CZoL=6BS_=gOKeM0(JVE1hC4o;C> zYqPm-KFh*czH{jj$^%nBhx8$e=NK;QDG~B(Mjo)`97$wnmO@vA`?s2u{SdXLxRn|> zy>FktK;f4y2Wi1fjp#~W4XZ@=<`jyy0|YremaqaXHdlUDdM}nX_TC8G-`RRX9>)r( zDp>)pAe6Wf(1k;2LnLF3Q`u@Z>go#xMjz7FQZoB)$AcKXTUy@}&W!#-XjSE)t*iG| zstO^}GCjeQ;earDYAglBkgbCJ=~Vw*i{npMgt*L8;9w6cV{F0MlUxSoPh)F4-V3?Y znBA-M2|=^G%9M6xngc}u-x?O zSW6gc_r4k5j#s6ZWEZ=NgCm%x-SoJzr4NKN&Yf{7O#SdcUCOA5`~3{wbE26vN#pl8 zp4vZJI-u%}n+;(1G4ttZeQ5P$T9x`&qn9(c3!kQQi<)*~zjn;$bIBJY0}(lG7&>t& zD~{j#Mi+8e7>?90^BIlDO&{R)D^~Y*;a;iOixWTAq|pJU0|CQzt6MY>U{B~3^xotF1)kqUNL_;%#zlG7f5NJEG(4TyP#tj$lHMs}f(ogEggaVw`V z{Yr;Ea^HmLc1x;80AjC^m36wd$c#noK~eN|dCb6U zzk6@meCkSC$iuU@2#{ZJ2IyZY2jF`O(}sfG6O!lIVrf4O+)XDYKPSDM3D+xdwr3c} zv;bdp!rHt@$$(@3;=!e-WRXu~*!v63S8I#pEBa+OUPqs)%>UKKRFgOC5yd9$(=DOm zD%ya7qlXJWznCRBqKJxohym`!b30*UAB$#a)i`&tgh$sxtN|0~=B#kze*R^6Wl;os zU^(Qdve++pd*Q0knQeFis`A}=l#vy{(!$o2ZwjWHJ4x(*VA45}_X}-8Md&MXs15B+ zgW9#beT56bQnt^}8h+iBKY3Een_7A~YkuGhnyyu4$IG3X5PP0uT4JWLUit33pJ>zEfOPxGC5k04jQr}h-2t&kGaA@%k)?uVU}KgJwUp|G zX1|o%wd70+p|2ks7tc0n+c-O-vt$unN!uQSoYuucLZ-b-EQ9PTwQ5OXH9a-`|1?hL zgyv&)ZMf#QmKO|wOHck-*b<-q^I^IgoViZkl4n8NW|w2V{b{7Y%e8sk)6L^Vop(rB z#`LHKie{TUeq!gpm|zVRxJ;&YU6C_xOpD@`7Q2@8PqGf)D3En-5xP%Ek%(^Y!`9+J z{2;VJ5t71a2eKv&ge$;DfaI1%nnV5snht zv&TjJVU6>7u`);($8FC%j$ix(x=+jt_y-^C9ts`YFZit?dBEV$e&{yn&WeRr^KDMW z_UEq{zhqty2Xy8(l*&R^Pj)nFeOS8^w)p&&g-_r(Ltr{c^(Ymp8*Iu-45Gt-(lu<# z=t!(*>P4kez3*PaCzuZkk|apD72Vj7wCVhURQ@$JauG+@8sj$yhn(+3HMbdx!fn~W@y+yM2QrtWJ%8YrxkL96w5yfS)mzKW5U1Z1+Bh)x1 zvnf)Pw#lL^yz&Ru5(tUU8W=?kW4ALYoHSkdZUr|g-LOK?J3(<65pr6lkYKH)aY5br zV5q#fSAb2E>1C$x6Cwcjmlj+DzcC#@lJE+h*@>zy=cKLSYPQShD&`KR)`~7S6n~IV2+k{Ed zD-R|~@}Nd+z#)FNBSR&gjCX+c6ECxcws7k7hX;yo%WvO(iKGFd=1LJQiuJt^HGP~_ zO=x|>sO(|eQ90y*?p3l6)>2VYiua#mk6;jT*oJczCQhF0-n$$@HYfE}!Uo-VK#Xk< z)N5RyVI~y1s}2f{rDY#De1!wa8ewl$j=htAICx=H?&P!O&DYWJTkEHJ->jInvB3)axkG;>5qs0l_DA%kph54gS3GLIZ$IelE16900#$`<`3 z%)tNmh#dzbrsKtbrSBn(Ig|E6!2Tq={#SLUD@qUIGt2Fv$q~+Bs!z+&fC8oHdG3ge zMu+ZLn&h>ovm@|LsZ`FEflRVEri1uvBi=1noPQKquCaBbmlrXj;H!YY8Jdjdq(Ek9 z`x9+J#M@}uBe*4=_QH2S5b{ed{q*~Zs*~3ZWB9?m%ULCfu~4HN-UjV!-k0M|G;eK_ z`g9(g(EIh$8Dq*f*KJE!Q4Mi;N`h5$a}*Lquj_9wQ^BG12ZLJkoiKCF;9I#oO38Vh z*DG~}mDSE^lUb$PK5ukg&NV-9#MhI+F2KoXtUw`P(E_gytvxTcW6tnSzum#^)-=Rz z9e#OIsa8iSr-T|$`SXaND$-n`)#S6*K%#=`!Rr!v5yP8|W>w4Q%@$jRFkH4G*e#N}p}`+>=b}_camY zG_ZXA6%H?!`x(LlH|k?8KOI3CV?@83_G%(|@OXA9eL7Xh8jmT4_LA^rgy>7XAYQ5V$;_zqxzehF2j{!N1(oR z1}|-gSDC1r;);1Z4?o_^{kUDX_){dB|P0?s0Jaj@bmx=Q(Na^%cgK*)0pnh z1@=vr<`G{Xp8%vn?rx`5;{3mdYUFb>zGZEVJD|kHLhU3X6zw-3s^gEt0IGzzS!7Uc zkqFN%T3AL0wYrK$#COdPN-X!IV&ug+ba$7!ZmiM@RELFXu=|WX{@9fDyv3n}^^7Ov z?ES2=4fg_XePVwd;2I~miVR?5mXv75JqA3~irC@DTGaBt$C3(+rJl;UPx5GO$gysK zIMETRF&`57m?zw_JXFIW2B-t_^d4hUWM*1Sg(<+~7uFm;oYrX&bdC?Pe7HP#FZf~6 ztb+M{w)<#1-;G+ysqmklY=4!zTR#pvjTCl~|Lu7tw9Jv`O76k2H7FxzZ=RYo?mIW> z1X99X-m>v56Q}+AP)!<%NBJ0TwH;l%jtWwwI;wqMfYkJruU}I*#mNLdoag4T@ zR2y7C(4i2^sOOGXd_nn{E^%}Ek z*1-=>9A$4-!=0Yvf8<*9Y%JYCUm*-tniJG<-$5En+r_{EN{~cErWb?BKC3roohOcmcp*I?I>m{py$OGXQPK>4Bv=FQPCpZHB9o#E zLxZm?w@Qwlt8@fGbRn9@g<$j256}^{Dn)OMI|4SKphv#&0Nu`=qvH*3&8R(RKQIt7*iy>&GJ)EDE1|0UTMOd&-0&q!ftg?8ENm>QSf* zF~Cwb!1(K%C)CoC#!DWg8h!XNEK%Vj`D5(sYtECEO6RCe-=$&uLp8GQQ%q`}-n}qt zIc}D zghYc$_QzOg89|W@^lkIFUal5l;(-@2>*Ln!dj^jS9G}0+i?+XUG6j+v3eW2TH0EA- z^%0P++uTlP9!opE@qSakg%YMTP#{)$YZv{KMJ%w}79TisR-Vu_`y0n}Ev$OBye3dsa;`I7Ont@NfGjdJeJ%tZK{t%CZOnqo3R#bYdB~EzpBAEb zyk8>{Bk;A}!zRBLk`>PJO;Ot`!^XcS8466YXG%g>sDju7Hnmz7FiRsf#_qe+ABcFU zY}3Vf>bHjLN;|L3FzjB$ri@8CUs?)sLQ7*Kfq~vgyQ$SbUHeOjnWwR=HSIQjff*JNJkOM>DD98t~ba~lA#Jjf{cFdc=QGo!UGOodRHVNbtS4F za3sWSS;3Q^%7BB{H??M}X-DOaPa{VVYxAo3)bb-1nA6rnHzDdX<}*={8oAw+sP-dR_!Gb=C+%pC)X%)mTqP{wI(AGlmH_IF?2Sn-)Y8p#_dNqunW*}u>cpY}?UIlL_u{S7jv zm+(_E1->%#!6X)vE7jO8LWqrNY-t||Egmu^tl(~C(5p%!C-3l3-~FCo^%|dXGXP|m zhqVCd`2OOyjs0^fL9o*jFe%qe4cPuEcg*TAKr)URZc-O`FgEV{e1t}?y%6i4e$yTA z!3Kpl30)rM08O$p)W|rE`%!FtlIt?|;q&oOjae?ZR2c}$rFY?X+Jd`&5<@e~IBlH> z*{B4;gl}qfu=yA?u;?+Xq8z;@7{gqWREzuExdcA6_*{ zY?|L@RA}zX#pL=qKFQUd0tPP88 z5|4z_gvs|gjOc37eawq6gw?yuUv~oYCGNXZv)+B|CpB2)L-vCqi^o%n~#~zB)^)MEQ4&B zZ%oxVRK+giKPa$~=X#^LCxf>*i*&cc=VO+RY^hdK{a1W5I%Eh*QNs z3I@2LQ0G#texRVK=EkCQr`owQD%|i&xFkLvKW*vtyhSJUN59oJe#%eDjdN1<`7fP> z>?`h;zJ(jU1AL=2*2hKfa%=!Q=39FVH+kfXwfTJyep`wrl*eec^$LaB+U9%q1N*GT zZ=|*NYZCQoE_`DaS$w#6_4@o1GTzDBmHyqL*%;rcgVms;3l{chJ4?|OJR?bP29Tm9 z#UUf@kI^7p>k0r-f5**qpCZoI z&lVH~1hR;`K$#Pz|7MH^U*sx0ct`JXieKyXuEaKdxQbq`y^TNo}h6>&LmQnNd<=m%)Kz<@z)J5n|%S_3{ovkj+D|007Ndc;T)?z$_1lnGth64{eRIl(YE-;1(Y z?8*zfHa#0Do#&Z4#QphyM4m$-tgksm+X|NZUY3?jDYo)`xH4qeg_M>3@f%7Dz6-Px zBj-5g?OiQqaeqMX{|&J3wHZxGQ>wZ9lhM!!^)gwzl#<*4S}R+^Jq&U2JuO2=RS2}in^AYrkAc(rcB3AoELCjasWCq&<*fa>3W6B%Y z%bb6Xc=rQ&IFI~d7Gzl27qvLT$9DFY1f@;B{^HhXi*GtV2_oV8j4e9LMRXZ|-?c5E z&8@Q(?FlUBhDu8V>%eW_jl^O1Hnxy})S1yHGpfm0_ok7xo=(x*_szN_UXQZG*=k5w zH!qis!X$LlNi&(b=>^fUBe+`J?*Pk$&-coP9~$Fc+Ss)N8jFulNw`#ZylggJ?XI`6 z3l6=Ke+*n6Tb~jF1m+`OpN*UdO{!TmU1fH3SJMA*b1Nq+^(vofYcwre=8jNSc2-hX zIXPNKDe2|{B-7dR>rEdGiyByODy|rMtZKg(dpX^hg{6U+8axk12JX&lmvoj<0ZPaV zCYVR~>d~V^oN-}%j?gHS`S7tSc0d|gH%{Stg)c=0ne-EzuGNtmZD+uH-5)s%f_ANs zMnvRFAgShd;ZAvG8!tNb6J@?4%Bd^k8{_M19M(-xqJzW_D{36C+Lcl!ZVz^cWCMe3 zI#zjte5nsL#end;Gak>^fKx74;HGJgm+xeFyqTZFTFQbkNu=@J@k4UKYE3}(^X`ge zT=Mx;B1`9<%V8OJi?v##?uMynbs#wlO@8r+DPIs(Y$J<~dzf2%eb;Ao3fJ-?QD^WboAl>uh@IMX7G$M-9IwFakm z@97VvV;C9S(Dl<}9ts$lPh6z8Gp>ZSw8LYY&I+?nzioUkhEgHTpW0OyNAKf%426Ph z>EnXqUokWRMOFubIF)w1UOTcUZJ6d<3r>)f!TPR*IK@$})=!*HMS-0v$$6i&Z(xmP zarrEoxn+V@4f*q#7Q`&2eD+3i2gNO6r-)keD6n>P$yN!%EIzLQv zGp{@bn8puH$2Suf**xg&9ZybYP(cl+oHCZnl}P3>G0R>0uqY5 zU{oD%_f+@juY1e*NEsjQqYSugmuo%9^Q${$n!=9LZztK^M;LY$5u zV6sxjM@rePgo=biL{3-nP8m>$yrqCn1v01_0mu?x zuqyY=_8X#*0qlEJ2zGjE)r81I_Xsae1}5Qdy6;RFEKIUJ-=)0VDe zZH$zPN;>BQcPzu6dVUpci#4iDO9dJHLUr?|8hc_48FSh5ZyHd-=KZWYRyNeIK1EBH z#;gvVMP}H^x&Yo?w(!uWj%OIAq_U@9F&YREs@aK3MNIr z;X*qMo`ZgW3mMFrPv+<8Q`g5KCK9ypp-rK&MS${*Iu4vr3@+6tV8F>lQ)V{O%BWIi z25)CI378$MD-|mh zv**W!>Aj$#&=rz-+{wDB(_sk==qU^N$y-dJ8YhbheMsm+x`Yu2^{gk~vuiBGM|m3} zj6n|DtAeHsUH663E=Fv}GaOH2j*Wn$dM#SIlY(c&n;53zKAZB04B4dsbc5sa*CAZj zbzZH-hHJ#xLUnn@PIb4b-xZCNmsZPe!*?rYt)uM%CEa*&oRY!0ajQZ7&wmbllS2x{ zFZnuy{Y>uzzX8k%VW*gY)%{_}=jI3;ZtDK^gbs`Zm`=U02$kPz<)VNo*=?obmEOC! zMpRjxJfEqiiec45=lScy8ngYhm;1dY0yx9;c%(cw4k&n}2P?gu9=9m%YyvWQvVL2K z)_az(raK;eY+b!KbNN`Jaqjw9!r+UkaXm+wJd4UDoQ32 zHNAi)Z_MxWaZ@E0Yc)zpl0kM*mKtC3haLRFy5DRXq894#jDT2T5YrVZlqa9uUM8Wj zMZG%ESPW!;KYe)TAh zcb%wulw%#R+xg69q6TqRdtYjMN|$YaVwCDTJtKISIKwm@df`HrG%)Y6@2HKuFEcnr z23^8^bnt%)C;(-M!@nA}E+y zsT*jA?gUray4|#}` zyuV~y!G2DtD)H=9Eo=e+?ARWPlnoirXf{@Bl=hq?*gC=PK;|XfId297B-GOdRV&Q2 zH+uUsY^;>LA>0Xd0KB?8b{8bgKP2uIFs-x;n@<$MwvGNgd|2r^XD$&_>McMwgz z1i+I!IpyL2Rggz_yKUdcdtvJzYZ%voClpu}ujR<7o%#ldKCWb4zYM>6NwkiZ>*`D@ zS>@c9RxdDXXkcszkB zG1V>~;3$)zx#Q*V2_^wjRCC@6{KoepUw{QBz2UY8Q8jDGZ0SmROey^=cb~lQ^lGHk zdEYvxw=}Cx0w0Y@hlzI;aTw4MSCVUslKqZ)0Dr|G&q4+V*uqBAFbUH7Q;mW|Rh1|c zYn7PeNXOT!;ZH0Qa<*mIAHSK-@kM6|V!r}T9ddDqd6anm#pj@-z`a_!kLwnVOZR}o zW*CHVX%6Iyp3fS{BNJyAMqs-!WOy8BBu z%2F~{lueSoTA|Ypb4*c+2SXP?g)-AH2q%#f%Lb8y#3w{YM;lEXYq_A=W^mpgHf)ES z4fm2z&3@C;ZWevKZ9#jnA(V*$s4VWihSHbkW`1XEw90|GN{SF#n2ZMGMnJPUYyJ?7 zl!BAu`xhV&0ADn&KvsBUVN60%Yoh0}?n7|8x^{_)th|Lc04j`WV0|)nUXBM{`Q~-C zMLx4=q@ZXH{*Zpg3LP=fcZyGaHkc`VI%JU;iqiS#SD#8ri!xlC;i#}`p6=MYBMW7>YJLNY;*{dE+FvSJ z}9oip>5K+sx&iPhSMa-h$qVcUSJKa3E}AgmDykfX<9$eIK~S`7oK!?$4lCg zQihySz*NnN)wF#)99qvtq{xCu;DGcDl7iA%q*x0yDOo!X*beSlTMQoyZHl?}t`#SY z&D&Ea2$I`(!QeclPVj^HtOezIr*kWOs`@dB5qHyX<81iu@m&)=54o(0;Pb4S*o3tD zG8kAhD%|JBb#C)JF-)%X^wKs{YlgEpQl<@J4JE;>h!xdFk-Qc0Fn_a>m8aVJ7%*%x z?DAMEIv?w0O`9cZ9M8dPHbOd|Pyt3dAYjiaX{piNGA(mSxv)UpXY%R;OY1kD+e@%8 zYMs%^R2QvrDuB}(#78;7>O`e6Z7+L%=W&~Gn-Xwe4JWwzXW)GKr~3i4EF|x1a0oJJDH@4L-+hvJe$Mb2 zuuI(Y*H2%-x^D0_ywLp7qZB2`w7hH(5NfD=ra z-zy*ln6*XJqA0e2cPjh6HFr@Y_oEs?7Btxbz(Z2CX&N@aEA4!4708y{`I<~{>C|Tf z+&NNC-B;tRD4V;Nf~i6Nu;qrP2~OQn-}v=+6Ybc93C?g%Q!1yG=N!R~vxY&T%QeB0ojDqdCC$NxRv1$;%Bt!9wkQPp5Bc_rq|?mymor%tigoLxGhD7 z8*K<73DbZk{+my6KLMm+wWBBjf)VjutNUHVy)CI!z0)gIs{*AR=>~>4Avo!_?XsHe z)P(`WkK^a_Gszt(PZiuPc)t{OW9I}k+`*R6=CIS_3HE(7%m_Nbwm;8Mr`#}qSSa*T zwyAU05c;y>Gxb{bTGm}nr|(gkkLog>x_|`@wcH`ZOAgEt(_>?{r6X9wBu(D&`W%H~ z#;C~r?eDuB=uk(pw1q&a^P&&sP^F&(4|abKb`uvEYz{!{#s_&yy6$_Py%{Fe_wd_+ z3GL;V;K(>rp0z@6v*oH#4IJPBESKAf{qnWkf2Na(Lzl?RGC)4+6W7NB%@3?1aiAj}zFu>PvJRc)BTVGT#xhzWF0S-f&?)3+CB7|!@d?vD?>CpO{T@Jj z40A6@EI^-P2?r$Xr{4z7j{GmjADELOM^r(%?KNid*?<}F!QN0k5ZJ%!vpWyilj>oN= zn3~wM)8lG6^a47+H0a@T>N~tApYc@Y6tmb@=0gJthL8yhs)WMQauYcCzOhI%2f$Wch8IXedZ z7NGTOP-~T*$FTb^6+=<3#C)de-kC{`ydsEYNbi5o6`eBn|L$rIKzh}k&nfWpo*i5M zAiRr&@>soazG>B$ZZ=Z#f$D)6q5Js}7HZ#s)+8gG4;p85-?c{uyORR4%_?8{sSW%_ zbS61K>ZLGG*ImmEfVGnqiKu(8WQMLfQGU&sn*+cmRATT)6-^vaaA2{r z5Gx{ZZ)u&SBcg9l)hO#d^d(J0P0Q)g-Pt?Da8jn!n652Hu$O4WhL!!%}j( zMCB)e)Q<@fb9}p1bYbc~LGtj6E|c3m0EaqgTMBQC419^##hUz{r)yg`f)NukTmCoO zNYh_FC0>2VIAN0F6lQ~1L*&fFlf>-Q$Qx@Cc zsHSF>2x;#J7RQ1|Y+4LFuCH-9Y;ekD3#w}HgVP3`WnExSwKz~HSn7Ch=kuoukl&68 zbIEZq|53Bi!S$IWwdTVhEX2edyF%_y|NT5H;-Q=D0OkK<>dV8SY~S~(B+610MMOnf z5XlllX-Bpq>!75_I`(Y{DG|MPV#uC7O&Z%+in3>$jAaNj_OXvKX6AQ4e16CA_0QY! zrg@&Z*Xug3^E@x`AK5!!L%K56z-Nsip0gu7t|gtuH;}ra-&j)q8iJ`h=?lB#uQij2 z-NxhMuyo{>dK}rOWvC4Y-*%(6OEMn$FlM$mJAxU{BZ@!{J>J;rPZM&wAcUM1C71d@fiE1b#@rJGI3)KT-MR(1Vz{WlCrZAySVkvZ!! zTws{DU!6cbO^Nezq%Jp&hFjtm3X-&ynF{sp}LI=APXWeDy$Xzf*fG; zC0bu(%zHNjp@^^g8P7I;9T`Z$!2$x16UMDdQ&tuD=J4$RP`Xi^pnP%a*jksV-RPgf zG*ld*^vAjk;T3sc;dtWgw-*E21p50*u2JT^^Y_y9NOKRb{+22WZ>?I;d zw-k&@Ivw58Moub}{FGd1$i(+;CKtBIAUud~w%y@s%EVdEU*2DrMIEc_JOgMM+{5N- z%H-`oeywoYTdbLp1(Srpk!E3|#*{C6?JM+!LV_1W=}iq`Y~8jTnNCCBip%8G8M>ZW zxjU1@i57KpyBO=9lqme>H^rL3TZnaVl)5)Q&V9+9J2qhkr{Nn?q_9pU(XJSP&}wxU z@Ke+Xl#P`TGv?@+m_bR%zE^~GK6OjMDAN%AstnINe6aQ85k0`tL3muvlT)u* zWnTNR)P3WbNzbK=BYJjTxB8E-H;cd<^FCO8Q|Ns(P|g5W#f?u|CwSYAml{%;Y*}#yd&~FdIL{@z%>zJ)eWW1hW z>9dkf#_dDthm#8>#h?6{!_~c5nj3~=apr1=J2D@9z-)~ox3Myj@D$$KsS&Tbl^VRc z+Hq$jnTIxjGRs0`i}k6#+`q>UR+TG!_j^d)yN>JU--@CZpIlSV_Bo*U9L)C zq*ZooQeH+rZJ1h1UEW$G`wdyWf4|*sQ>YyET*}~0zUJ_XZNL?afd|yuoIvgiAM|b& z8^k2VJaD=KEDtB`D$;E?4GyKq4!tCJ1_XNdf-ZN>Wcnc3nyO4-3EP~Li~}W~d}aR= z&OW%b(rp#Qtw_HT%g>P&82CL!p#+(L2i4)hdh;@z|5_o$trYYFJAc-ALfBz=?s~Ap-a4^ ztG_GCwS?Fluj4DQelgeFWsQxwa(J= zD)CLZFJ0KhSQ)%{%kWK7qkn?kh8OaPL+&6OuY$72Y~L73l+GZvRa=hAIyXx= z;QpjsXio?~%EPE&4n-1Z6LX44>532c{v8AA@Qc9iv-!NtI zF!Y}MCV92U6F$h^!I|Fx?8te8d94Ag=e|>`nc#56@;6_g@FJI^)Gr~aLzeP_sViNI z!-l#yVeg3J_6V8sKAOFe^hEc(70!I(C-0{2X|+jqAG5gd!NMbYo_D6S`S?Ra3iE z$VDv@E^kb-dL^_jB*WiKjz*F(sg0Jz+2vGI`HtbeidUluibQY5#Hse>HZLs33)<&k z@u}C~8O={8)2+9zp9~@KfeXmG#toi^@!ev)R<_x8IuTxCF*hZ={JBSS#(!y@xs>x| zc58i=MA#&*Bbmk$-WiH38G3d_Q?w{13Hu#(r$#C1ulya=<#&hoZ13LfTbh zJO5Ut{xCyrh!!E3COlRS0E33{n_ZxkG)4kp=l-;*3Zu@#XgOmfh!Ef=)q!*^_<0mP zTl!M$l2G*aJL3&YzT5OD_@*c)_yXXr^W1zVm78__0Rjrk4ChJ#s>TajW1h?ZZZ!nvM>w(eb#D#0)i027F8^ z4Kp9C`)?oPsxZH-Q*YC#)T`D}gH`42Wh}FXHp}sg(d=An-W%67E_424;gHR&&-yzV zBsWqeU+8%wJW{txmV^LD{zl5MvF>f+lxlR+nz==_KLs4@9($2sq-Bkx;lx+-=)egr zxDSv>R0aPQocF=jJiG5`vhf3KQE*##2wRt=N2vcf}VkO z_7^%;FMJ#QcK7Z<5-~0W4-ARd;vXE>P=|yu5EH*lmH5W<@hqwASw{-wcLG_YH)?ap zI|2Wei!IqD;ef6lAC26e$hj@0QbkJld$w))U}~$a)F&zx%dx7gZ3LBTZ;U{Jp&M|X zY2>mntImztJP6;sVZGhce};_$z%9`t#au7^j8@`9goj_d;?@5DRcj}nA+tbud%+Hr z>n{LiQK$gizr63%P&bAol1bZi z^C;R;#6kt8)G4~X9~S-_@qfc!@Zk&A@1L`yG_MJ!LSZk6h&Og4Vi5LTwV);RJdxnf z=z@y-z#Cfbz^5_4<<_hR7V_+Md`d5x1Uz;@t4-Cos#1RYe~ddr}Q#;Vx^4 z8z_;g>oPZ9HCLB1xj+g)F-A!hcLf!2MvVnh+R-KZ4LbT4#_Ot}_ZUMwh5@Yp3m}An zi^g-L$m3-XE1G`QY51}IziU2C*<{ie_y^iV3b7EwC|N8VY6`e{ZBuyXt-(7%!8(ZJ z+Yj%=@lOo@_g5mjf2B!i$+cwHW8IRrVC@+s0tv}%WM(k57V@oQVR;RJ(2S;@qo>%mAOoCk0V$1kFFycogd{Es z&>tbjLvAPqQO^d&gcw13*69+cKmjZWP213VuvoCVxGbY`&rlv zj>X@w?bU&+Up7%TKkHyNzTw&W7d~W%02$#8zrJsZCYuU2n%9Ol#t>v zyo~jSnrGn#2rh}Zls$~xyEscu7Og*_59GThV?JzuCH2A;*ct9hFBA8@V$&PCM=_Kr zZr;21iy{KhH+S)vwzEO^(MJaO{+iVh8v+mp%X5~Ls)nwbX99;g^4?oUR2pO;fi!iG zMR(*u9ZF6$xz2CNkkv-b?Vo3{`wy1KP}|x#zeUWQ=h!B|0XY+dx7InQ5WMsYt)v#0 z0$0T4>78fc@`HI4QtN@1eHLxO-Q)wv+6Nb6nI$Lxu6whPQW=DqwaG^48MbP-}cIa9dutMqy(4x=M z!amM}ci|ru?$4a#VW(Xd5Ct5llENS%|Y#_TUw>(o3Z2St@IH!`dTlW#P15_ zowZ_gpXI1vYN!E}etF*#@m%;@t|{p8rbi6Sq+tl<#O)Uw>-c>-h)Z3IzK0M6Xjz4c ziakm}IZ)-phf+bM*kmF%R$?Uk zp#6v&7G`SA+9cwz=Su+;yY>U_qlb5`Cao|K z+4=6D7nV{6)TMBBa}ocg>(~2i@9R5PwG-)~gT$(mm2Tyqp;C-rUHm7mo;=7eB*&>B zaAM{SYndtlsZYA=UU~0nxTwMO#G16zZvHqWUBfrO);Su-p zBunr7v5D*?a!3 zX435~Ni*kg4`7KKmlYc(zk}|};O8+1;E~7Yvg-qVtsa=eGjFQ)0i`TxvSy)2d$EC* zt^KrWFFa9XYUFM&x4mDJP=vmY0!xK{#!1v!O*zCok-DyJVFb;GF zHuZX#!HHO)UI{kvgMGE=AlzerkB;ri30OI_H|Y>gbm!?80O0o3lye_weFmEjO?jUW z@b3#30o`##v39y_#{7f0CL5?4cZ5O3tqZXVPAaD@Yk>2I8aUI0ifk)^tC!W~ln~D^ zk}=#yI$nK{dYNG%cy#)6*oJ?PfB+nindb_aXN&N)@)28r}O zs(9GzJx}5NY%}%q4;$JnrLk%7R=CtRr&qb%0_gpEeE z2{acvA-E5x?>uwA-7)^CF<--MaFsRb5NqGY?{Uuhr_;NJrS-3y z1s*(Q&utMQ5&2pEJR~d1Hz{s7MpIjuI`w_ya8Sta-&K8awUCq7rVOCb%>=*7#hYLD{3&i$T^_>IDEsR z9+b8A>9t(^8LpG}E|*sYYh=!92u^k@!6TObyNeaFupM&7)^5*tFhr1?yxD_!q%zooNe z!h7^_1ZKm3Wlx6CKE0vSX#&B&LYDz=PI%p)F@Bl(@a`K&%;?K^6(VhJ(XhXW`hAt+7> zs~LOPjAesR{c}jqT?4l21$P`|@T(rCcO310Q_XGi4Um!o1+dlG75`TofE;a~d9uA& z)~7^scg*GG9yn$6I!-x=@DF6_6YnndJO({Le z_Le271vJp z*y$+50-^xUwc9F*JF;^{*1n&~g)Ga!-*i-6@V}IU!tS8`c^Zg)5+I#Y)unJwwY6?d zw&q*BM`vxi*l%m)v$5bWQR;VIyG)p1z~T$JbWTCWt^TlZcb8wZreTB7q*qP;NcZZX zKLylm8Zdw<1WoSmy@9+s!kn)8Z!V45-T2uU6sg5`vlifK?1ToWX;6kbSj+X39CZzo zGe$e|$$GaL@Ur(C)n-zfI#yiZcfwP14!8m$R!FCS4DL!OBuZANJ zt#Rj*Sw^6t?SaaVijWSez@iHN5~{j~dXLq!-r}i$3^fyDk@cOKbOH5)8|v91n}AUI z2r5;%fkn4xBF^vDh+d|dTk+NKsIzySxGp9z6b;{@Xln(l>^2V3nFIxcB_z=JL(9xjZt?6B$_}TU(TJ2dE zds05IOc7w2=3L#iS%gl{H9SD(IpzEH;u3AfKy1tnh}?8etDbZugSw~Q+jgWH%X*oX0d2(RWK>yeS zN1K17^L&LD)0XxuFMzt~?%Z6E1mD&&;zM#lG;7~Aat%UnyBefsH)=z<@Vm!Uvy=m2 zXUcVX<6Ze~ge?O}ToNuA@H2Lgu-&&H1GZjR@|Nd$8sOXS4}0?Q!tBCGp z(XO=X5@9WmxN^OZM#f@6)3)nYVIPj%2>bJeqzUvHfW^4sZ(1_Ioj+cW-T(p7&inoE z!y#R4@>UFw{yJ6W_G*}r0dH^k zrzC!H5uhd0D|N5`3rK9I9*?PzUjq^rYf_PL;}>Z11HL>M28Y z4$H$a;At9`T!m-#0LLWf;R}EG8Kf)JNF`S<5#fsC#rPWQUUp~OE|%Q(FT z@R<}gDWQ6hI0{_cbq%GOg7H6p)jCwo&5p6ZLx2N@yK!hORt=@nx9x+cyvQpNY2?_Q zEXUK(gV}zx_o4s23)SuVsGOCQCkdj&yRN1xd(~+36(OiS6LheZG=!d-KTz87iE=tC zmR!?wQh|_~du_HkN(he0J95`ndL}fXj0Pn%E)IjM)W0n-VqJkG80SKwrog_FL-*|P z&>-U*SWoRd`(ik>PWZ8tIWY^bOKJtAZa$Rl(OEqz0>WTdJ@5R}yy(Z(p2h#TgO1&c zaRG+mAy#4+Zx0kh*5Nhc6iE)DDJWoqQXtv!`W$zEg60@kt0LP!1%VcNnQ5a%K}5x56Kak0g)*yOwf8md6jaVAmC% z^NaFnKsGm|1%C&8QOK2Tcjsk*STWfC+^Z@Rfe0Ib04W{VFKdE0Dnv}V|G_#icdU%+ z6=pPie_=%S2+Llu3Xsz>}@lV`284=#Tp@jy`@$a?DFGm*Z)2aKoj5!FNU z99qO=?;4E{{9v)*MXW#j5G3c+aCiNlURq>STBv4){BBF%J!*EwoGb5w zmoBFV-vKNCT!E7h$j_G>56T=0BoYvpq>fo*?4^GguI-Nm#5Ko=wj+TmAp5b-4c!Bp zDw_aenvqq~muC+I(Hz;g>zp|x&&1)~wYh@(0^0;v-|G6`sI*OzcrpgrCQno(sfms& z8=Yr&?#K5lg{$?5^ztAxPpTRQ{Z0*7+7)wMOaN;jir+v=1vEkmbdq5zvB%`X#tq*bhy=a;UYxP=0eg!}B~#9DAPst6^p}*QnmTM(tt!>FGy;`|8`3{PwYcK_J{bUij0kd^_Y--t4L}h5(Cz? z+V0Chm!e+zRj&y@fVQy9yv~w>z2xLULDD|Ecl_$-RGxm!WzV$F`hG$%5!09#1$8rp zoF~GPzevL%x+E}j4t8zzNHM-q0I9@)5K+2-Bm1huf#5!;A#lr$Rdm>Kp({x8<{SRc zV^b&rDV-Z%Lo{4yeJYYu0S4A2&=%vV*ZqieOLIme5zlPhv~);8+PbAJ4oixABgt7E5Lx=z;whzA?~yLrzGa`YZAMU=_Kx^ zs%j1q{Cx+rJ#}G)!*c_}C%FMnI9g4#N}TW~WvZKZq*GFN^J}gBp$rf*e%n2aAdxvs z@qgks0bROON<;hd7Vv;zzh(JgFs}oo2C#|L2ZHtas(^e0yn$?*leX<3z8H+L94+vy zRS7h3KYWt&1^O9XT`eZ1O!L(gpc@JEE0HMs42E8Sup=adCFAyne?oIQkHtfp*Oi7l ztbM=1yclMt$t5bk*+us<{D6Yw{6KST;!$zw2NAr6?Qtanz!}~k=x$!$35}@8>JnqC zzX?ZRj%R+kfV%(n$5F`!SL(oR=>`lW_gKU@s8jaq{LP$+Nzt(Hb(IIt7Mo}T_g*na z!GAy}2&ChQQ?!%^1oZxPH{?{t+uyyc=vw=az5ASY#y<$={fIRv;aA8eWWXaiqu%WE7PK#-Nlo~1w$facLM=AG~$_F%Yc=6RJdgTV_E^Jfq!F6}`r&XX+^1*Q@ z^)y*xqWF(}+9~2ca;p!!>vynyZQNfkdp4tN7#Il1e~?BYv|OhFIKa7fy=2|JdkCET zry^5mZ=qJ0dfREVL&1H^e2rOP#EIZks{4)4dIp8Y+V2+u-v@*M23b2XDKzy#(jdso zIO^V*M1bNc2t5WCUY57stlcP<90_mY4YCH!;#(7TxyT|M|Queb=am+tXlw5E~5=X4;<2bWy2B4rMai5X^T9YxF+%)IFk>S~YU1!nE=#fc4QjQdUn zuYomId*pUUEhxCDibtx5bL44U@+a7V(2~$EqT$i~WAXs%8+=jO{v&OkeaEVV#e#B) zEn|<=mP_MO2fimvHcV_!qhfabwOa&^5Dn3OODiZ2{#;%E?Vck-H)4C`Q}s?31+2{E zI3WXXkfeNjmaaMQ`Tqwxda=XNlkv-9ViE4LFI9YtP0^pr7+*(+y$)hKJ3V6#+==IvTx&-7Ujs4W51;nE)Kth5G2qNC*HG*#Ea=+c6Sdfs0`&(adO;q(!P^cxpzi zW}tV?2)jLi67FdC?*i~D+WK#lgrQ2vS9piTDfepoXnX|rh{&hZ+b3a;*-fOdg94n2 zZS)_|gy`*Oe{trGFV4b?E072yLS)ID-`_v+rk&^$)=2G4?{X=ENP1joBu>7|8L~fu zDPMWxl<$hX&%UdH*d2reN}a`AVH(tzs{Y1rl(hGdMqD8OlMB~FJn!;;|F(RC6z_Dx zc=Kzf*5dXTrx6Uojx~33bP9s7PtDRP1URtrlX_EilUpK_oWdOhZWqlc;h=|5*T#br zA4mteRj1#d;E`ze{Ux@W4QV6-c>a-I-=HO@W^OhDjQ+UZFrI5?$+vp-H5`aFe)1=A z&)DK|ugxn42T~s7w_mdG0E~U(pkz7ED;D}}`T|`IXV!rFQc84tjD3N6=&j~_RaSG* z;LAZ;Ia6K)JTjsEt%AsZMCG$P)7@4^K|?+Ww*xn+S%MUgxZSsDaka*}E|KEc4T|0@3=Z}unhFhv`v zUN3Q@3lyxb4gqWm*lC5lnoc;ua}UsScOE${2hK4He|3j*&&dOZ^>A827dQ*kAAiq| z=6CVB6T$1S$Wq}t(cJj7f8+R08G(z#0dPp2Kh1?i{^5wZTsaJff+aNSRi**kobxmpw z?SV>dgL={mim(lj7$C%KIC(wh(uz)8%jgN@Km|@~ik@$Y7L|zwwMmQ{xKtX40g%v< zqQLeY4f0#AMW0AX1Ybf%*E~Sjj$Z|A#U~8Zr)#9?r_LR;Asq!z4fNIB+p9_&WO-^Q z^+*G+J44XOtDs48QPtkba|hNuv=)NDgBsjXiPLFG_Chl5=`XOr$pUZ-dydVRE6wcH z!k>R~_eOqwqgxjOa{7TOIv__HL1{MOk(J3<-Gmh7Q`T;a0OB$b`bk~@8E2hQ zm@dH7K^^)iF(G81umsDyL>dZmj+2e`6Wp?gSlrLRVC#~;FCNyYy`I=P4*H;y9lUI@ zna#DI@whuXgRS8OR&;Gb@6Tj2p(>Mp^XGoEeNxsLhk2FA3GN!jmH_P)?bpbCJfFtU|U8)OKUv~mTQ|CIy7d+ElA$9k0BKQ7E+HH<)UdE2=8 z%E!j*Pl59gK0pEV8DM>$`KZv+r#NLofzI4X8}%xHC5Br3sT7vk?IYVM+5A!c!@yJh z;p@_kk}8Y18(F5+Ew;ki`rmBYmkW#kYHh~F-~F=*Ztpfpb?WcN4!HyoG#$=7^rg3?3=tlWZfTOGN|CEVZF9~@L!Vv>Hml~@`rQ6fV>nWW5-(8q zxXx$YVM-UffnNF9MIRj_U^mG~?DlvC7GCE=r|d7W*j4@qKp;v2UzxLG4_k?XSL0oQ zL%|h)9s`6S(+nc^dW?5~U^I&TNkStIA2BW&FuiXi(RCJxQfxsA9hPb<(&iUSIq>*n zQ#cRLtDDV7r}OX>e_0!kpb8hm|e4eQNFrOp$nXg7cm%$}d zLj?Wf$lKQ_ag~g2p15^&PqL0!uh~6~^>rDhN@oAek-PLJ0=?F4##mGa>ql33lL^XF z(DG1Q|GcMLvkE1BkiV;19PqdLro7V{a~+kM3(>b``3+GPWGHp_VjIoq*@SdyFrFX z-e=a#ge;B@bDAAQ|t1X?U33vt! zxmhS(bh}^Ih8|HoWLPz`CB3c{W$_=t{guA+?74lqp%+UvS||!jLp_33H2Q=R(mC1=XM03V`U}I&koACOKO-9mg7PZ3ZNnnW^>{ zucSz?Ca}zUXE$j$hP9zRW38@0StNv0TrWSP*d`cznGV)S&M|Zmwn+Yt%I`DKoXLJDRr+LQg41niHPrv(fOktmkwM%Jr{lCAEDP|@hTQkp znY@ZazmIPNbUeUc7B=nC^f7ugF)S6wqxseT(cq@xP%Dz|9EjPT?wbGTz3E=HPk+^J zYfzWzMh#}ZY#Zy^8N=>JDqsx10JfBUNR*ZnYP0|5x&wIP-`1elP>rYnA~Vu&yYoV_p92QXKD2$Ybd{lhZg1+%~4Gdl4Wv`@AUM z33Fj|ZYX`&z)Y+;VExr_4FwyhewsX%Ga1eyH7!4S=FUUqK7lEt*_;p!zPsOG#0Dv~o zgw0LXI%DtNb(o0p@qxU*Q+`gsaHo0t28fO89cuz8;IpW{+3WoRJ3H+@KBO4Qr?4T{IDY z0S()vFUN}F>rJtO7jl6qWfE|%)hyv*f6#u(@0fD^?E~wtYXmq*{oYBBqvT+H-^1gn z9eCH373DQ_ukWG)&C{`#v7wmATuIc8uVgIw-N(`E)&I(ER!@?5WHa4tJ9R{qG$>@( zI=X;w)41daYI@y{QdfG|9trg67oe@m2e^+!c7e-|?UIui`$5ROZ0!+@mI7nC=h?VN zo?Q#07XY~TI3T^yAylDpI8@L1m!Au0@jKqz3h6QWeo_2aG*l zS9`U5Uovbb>PQq{fkn?LgzXP&yn5qj{3qfK-4;INrtivNX4vCp0VnYqE4Do|Ex z+?FeGhUP_IhjL8$j15%0ef^v6!&GuP_}T%0Ct-mzhKk?xO(1V8>YYn|nlqbKK)2LD zFd3_HLN6Y)WA+5!nMkClO*q2WZR-NS+(-{a(|aw$b%k^eC?M^ifeE?_Y91=5!wvvT zF@rZ{dmxCSTk*k9vOw4y!&`HZ`(lwcp_*IzCQH0)tC-|Ka%F;RZFFWfz7a8yc4!Cy z1_$Zlsw_>miwh`WD+ClO7u)Ji0Byf50M|ip&zW^#dKn@e%I%s#-1`vd1I+|c`?hIy z&cosF8#!zqu*ne~+KcV>+54IdHkdC*I?;OUe`>xAUTe0krTX$YcbyX@{NkM1ly^6N zNJ*LNwx}k^+`6gYrQU(wHvuV@3DsuP4(Ax4N{}Nl)BfXmpD(I(uRZf=O{McA89!K4 zeCJc(c`yTU9Kxn(sF0UOwA9!XD3Nl;)6UpGTU&BOvIl9Xsa-NGa zZn-MSL-&6)-g}$TsqV8$+V#&If=vDfJ_gZ&-g8F0{02y6q(Q~uymrEkzHx)NKf0WU z^Umad1-X1zVAbj?DlElgjL0Rl4@o*k5V|>)X%!PT-GdEaZX8jKJ*E z_ktxUWN`h5pLtfA^7A)CFFkrPrW3YLKh+MV?DFu}$3ch|urO|f89=1Tn{upE=spP1 zSHtRC^4E6n10!d8CME6?vqOEi?V00_=V8P-gV31pAgLmkJsU2Q4QjD@`OmypEID&2 zvCwc@!d(CTuzK)xc?)mrQf`nf8Pu8$%IMkxb8Ojdw^5B!99{iJ!q6SxxYX}E{aW+W z);$)qC7_RkJC`~CNJgjRt?C73moW{yz4qzUGC>Ts2z}6g;P9aQv{}o<)j_laXtoel zq1(G&JFZx}TACnAJ09LuN6BMY`^*vY6+?GM^=mYk{u4g=DepBnhIYsS;X$00uSoz^ z$hl;%)}X@wiCedWwc^CNn;-w%)5I@Q?tL7{glT#8`9z16zXsm0r@w6B>f% z|7qu99&jxcm^~w_tO_Qd3}jox2HU?NWmiTJwFkdBn%Uj3?+pPbNtvL6Xi)?;8)4K75`BipGQj|e%Ere65c7j*PZma&L4%u@Hu=lwwA^hx+aTN!#Z|GY- z=KHzKy|=DjNTld5Be7C9Ys6MYU>p?O3<)-l|Sk5>yZA3D^P<~6FUwEca*1&<38#5L!jm)j3pct9!?EaVrN zj2C|k>TO(m@^mTzeCNl6RKFg7y=zs~{Eg(aUC!1@X5` z?V5pgpp+n4i`6rVRfS#ika4jeD~IznmmDkvu;qhFR(u7?Fxvfvynr;E?`FV!dCPuv z#4nxIZQCO$-7j7tdQr3k>0g8gjq^=ZJO+I4c9q$7DWzpO2)p@z}nd|rJ+ z9)Rcc>2=EFdL4TH#vFIfhkqP-iMjJf7+UwukP1# zSbQ_?hn1UomC1MeB)@oPoKNoz1))_L4e9K2JOTDE-M#;KHRQ*c1dsShJ5rK=c{6?8 zzp6)8cxJ;g3(DuV3O6n{rt`D`xt}z*sL=b+&u$}U){>pFeD;G_n%ICy)@L)wQ)m9J zK+S|l5s`$)zwY;svMJy^N~Fa4;_t~~6P8}M(`EmO(qvBllSiRcw^w&!F{_@s9F3n6 z9!J~>lMN#vA7Q`M=QBkhf1h~Y(4{I2X2a3i%|7rh!N_OYg2pl07T+k9COi9wUwl^1 zQ!`@oA5h(gNdddqm~##>_?f`_soB$x5PI&xgV0i%0lOm&UMl%CPPzJ9=WUZ%;;I(q zMS{$#SMl}Jlv#Sh<;xQ_R*wPz*bhf>Pg^a!Z9Z<+vXBeU^10nxR0^EwuRJbQr%uL( zpnRryBd3Hn6G*BZ@+ej2?=E*xX}NqpS=V9I@E6(x#HwFFg~bhWLg_gph}wJ7;X}Z< zG?^=pHp82(IMS`{O$iNBg*N}J;Y z9T#3F+(%oCmzrpd_aSM zgR5;xfdEx@ubZF%#NhM6!&iUSQ@266!su~22qbRr5nD6sxV{duSh+jy0Zyn;bNR)E z*@>m{Kle4Rlny$S)R2u!Ez`=QV+ao12`@IrQyh8_rzA3GUYQ~eDB!GAGDne2`i=Z| zaZPy@x%mrv2S;>i!d1uskpqAFTLPD-|tR(Ah7p|-eV9ii}Bm1uyJ1H9zd`M{_{{ml=7rdBOK5f;D7m-wSsT7LdM_qvwnLSs*5Da|$mwuxN zuTly;G@ZJs4}qKRo{LrampRE>Z$)JY0YKo{{0gn;` zVMXQ}+$#3<_oD3qspIraD1O-3?!D!rxfYN$Z!hT^ahV~fVLqz>TfzCmKQ zDFl`=!^T9q6+d;Jfb6-gQ~(j_mpLdG&psftA-gEGwKc1E$j66u{TaUrHH$?fu9Zbg6Wg1u8FG*|@^HvN&MXs!Im3ZbL3SURRVn zO3fk*%?D$>r@~gQp|+0tjq%0IokINZ!cI6|@U`q{RaoBy+Lez*iK76xcz%Y`54?Y7 zv7U68I4VS;ck*dTATEKnUmfc|#j_uM1t_2#h_eNx{>4Jc0!(Q_T53_{u!o%wjfXil zhTj}6RS%@6FWStcGA9*EeLA`l(|_+D2b&vpu9ViP7}xK-UDD8~$hXa8$}o^QM#?jW z!LYuwBf7evEf#*(4@>cDi{Abf9cNm*)n6)&+{#0mI^t4aCNa^Mkd^*4uTi5>mw={? zOO;85e2=L=Bw=Fy--E#EdyyJ92h3y{ed@wRiC>(2X(LdF%=w=y9fb=j*Qs5M`L5Q= zz`4rvS5h>qIH%SDdTC@h{L$u9BnN>KKrl}Fud$+TpB|#iz%23A?JCgn&lmmm)?#6W zNh(!LXxiC?7~s*o0P813&OeuL>d$d?N?_~$4`ncg^-zkwoL)*TPvipZHzBJ;9t~|D)@mbrX4UhiH!*_ zNM+735ma5mHzya-W#T$PeUq-fMGtGKBQO>SB1LZFmzqJXnxvVNA##aAc z%J@e1r?*l1g<8pslnmT=orPfLq*dUrIY%4-rkNeFk8qnhjcXVc#E&wfeS!q4DVovV z*#K0&;X^nq;vd4J_}g}+-+qUj_QQnr0j34@J(;?=f@KUChAnT6Gq)w%POAHMMdH77 z0VyhOo7T1NR1)}TCR2O5u!PA+P3BO~b7$?#?ikTMeQPgT>rSL;b+CeD(zEnB zPddukuce(rzK_80OUZ?}t zfIcSW>9K_ScO|XeVPHznv^f(^wq&+q?cU$b<8`_;;otnW=E&bJyk|}dMGbYm=xK?_ zx2q>VUzP%9y%!VIP>dH5YPn?j+J##6@6$}CaLGPdvtG7%sboc9OI-lrTbV^tEoX1( zy#q?&U#9;6FR-{Ob_*PAgpCA6teml;U!b!2bA!=MI(av${CQ8$xaIG-@6ZS`V5wrz zFJMGyRQBAwUCj}jx6hC}lJbGYV9MaUkB{t~BA1e9Gjhg~?Q-Iz0>UZOoXb0#G!`g*sc_rZInHrKh z?JXc+?-guXjS_IS#D2acgoPeZXTbL!z9`(V(h=Cj?iUE}(RUoqMRIq1T<{gd4k8+C zC>F=-X3(}B=Ja(Lq10x_t$>@FxQ^vE*xXu7BF0y)y=|epy{)$;7YkIhH=8qw_3vW_ zQz8dxeufId#5b-E8};zxsi0~BuUlcyarQl~K?lwj{ZV24>pY8INP6#7bRc<^En~mW z*xxYO`$K!bO#BxuQvyyGzw(Q}Zy^OP^iIt&$EU)5 zP3B?D8`SMsl$2w<4QJj6!PL+@&LN-Ydf)a*16~xi7l7NG@*w>_XIsLkaIUGj?h){Q zb3U)LE2%K?yo375sACCD_j1`6*b(b(Y)ZHiddo@+ld)QyR>&uq9}p!I>I)hg7*>hE zI&yu8q3v8B%B7wh)ry@vHU1;=IEY4v$Lt1HII@Y%ggIMZJ@*ovo%;F(c}|xVjQ>O% zJb4-Q8>!1DHv$N%W60Bp4#=}?lK}O*x^v{euO#`i!4oGa{>N;(@~|ts3V!~{LMtu` z6h06iO#a8Xc0>;0!X;}qa17!!w#Q+v%vBYU@pYoxR3=q^N{Bh$R?0sHMQ4frq93sE zfv+6t&n(Pn*TdR;j(to?2Fh0Yup4pK+INm;hKPCSwVmd7uql={OY{Xs21lR2hr_~i zpDUx9;`3w@Na}v&vfx(0GDPd#Pmg)fO`ju1G~i&^wqvo>*1vI@!|JPVkrslbgMMSw zWKwP&mK2O%mB3!Re56Ob=&9mlV7{%Y#%yf;)OeJ3!7=TCAjdC;%`X;75S zKkp*%R>+QWVZidm$r3S6LE z#CR3_sUBS~aRaKy9*L~)r4OvLT}F#-w?{#l^a7KS2&bj2sFA(>B#E<;Lo+uTAL0mr zXOk_P!-1+|v{`kGhQDt}J}r0E);z`HV{-RMi#lVuvGr-`7)St1LzPLK9)UD3OH`pV z5`kF&R2|;!)&O^eOf8_Bd=aFQ10cN{C~9}bnNs|TPe3OSqc0?Rj$~0e-E0TR_tCP@*2ACsUWsWZR}B7tGo(T55mL7^~?q!q3+{O^NuC7H84sL`QByy zWmo#j16kM<4vl|9JB!r#bI025$b0>+tZx<&rpN!f7gw@Ae0Bu5VHK?fPkcX~>U8%y zsBWls|IYYZmi^`w+q10cI)jjsqo+%5M(}!kF!>mHDNh$A0&Fn6`*>9?YDzk&@6HC{ zOIT*lDc!YlJ@RnI21n-KgB(8yKc{^UN2tNWnD0{_cqy)_@psD(c(vu%C2sgTm65;z z<=Ep_0S(H1Ph0)J{;~mnJ^06Vp+O!e?vlBGYA*)32cH02*Osxf-l~S8+!8etEsIh^ z(M)wtEsA=;CiW6@5gzz?Cu5ELgEl_O;(Pk%ON&pmCN8Pw2l}276%KLl?@B#*^yZh% znP1BwH9LANq5Q0MXQT|kNRLNemKuE#yX_;le;)8o^sG`6mu4D!On9o!-HMr6`dw_> zzG&lWTUFz6S?SvOFQk39^?9B-UM|*f|afq!&}=iCw9F00Kb3FZrt&ax2V$#0V;^ z@F$RT2;|xw1io1psDvdU8c_oW&cbb`C#R810kl|`xeu}~2Wu)s)3*Px=Lf&|7w_Zn zo`7?=u1SpoaBS#DW4?}3PIkgUE~CRZ4)qa1qdbfy9^l$m7R|APs@3)v4F* zjwTa!oeqBGWLevEUAs^1s}W(&N}z{JuqkAHyrG7Icn%59pcs*Jb&&L}WFPShNP!#w zUGZz|U^q1916EU@E!?|@l3zi2yGjCDT#}`7MtAMxaFwUvvj6CrpsJV7RZV_7;@U{U zXK*Aem}XLX9!c{g1qHUgsPr1~FrGxlcPDe$-cPi&$EKYt9G^g6ao?{SzY}!s#I_7nL^R3JVkND(_Jl1%Axp@*|#{; zn$3Uj!kh&AYqQl*7zIQFWHp8Y^^_8D)SuW0v|6@5aY3#H2m~t2{FN`B@oVgY(dKph znl8`#8PR?;eF)*8xhavlwpTzPZ@blc5Pp$|Ufi*ZCPkJ6Xt8FjR{7!`s|1Oe-@@DE zbM$CHW&+t3Pl4V7)2FP4C8e(1ojxe z-BHd+3Y1wZI=j^LRD7rG;IBZx)+=FSi)3PgYpH}u=>ahuIJkyWFsD}Q0bg{1TEAzW zUUq~BD6TSkv-bMZNKi{jwXhVGcRXer4y!5BeoZD#w$4iGaPRT3hAJ4g)8Vq;kIMv@ zYhx}IYjXOc?kEkA6cX!z({>$>z{uT!Vr89xPV;1M?YMF>BA zwc*hBc6Pr1{=92lWRBF9U?sgCUS#i9vv_pae+yc=mHxO>2OtL*%-(!DZ+A~K%eC1= zuD{~g&zq|`&33+a>vfGh=Jy$-?X-5?Ic9!I=BFs>@pH*##9-&=l_CI(%{RjQK3D5M z+K7{oAsyR@Ib~k|aw`^j$aZ&bcuMm$TXYu)6YNL|JYZ3@=KdlMa+X~LE_{}4`o_`< zz;<`;-}WOv!Mo@=p5|O<%LuDs~F&nR|>jnMI00Xc`xW!vhIO zRv?D1OUfdGaPtV`F+@LXG6sRj*1!C2&h~Gqm{toz2}`6cb)glld-mkE$)63F+0RJQiN%-Nd6GR zkC?5QhdgbaEsV@OCxW7Si#w5akw_I(TKhNnA3v^1zzvDu072x3+712S6ro^=ZT2TMub*n|Rz1Q8qV_w4CI=duCIq&+IHDKuDJnoLm!pCF>kk|i!ZOcj9Yo(tX7`eP zF(8t8;~@nKs}}NuiU0Yz@+dZ&Q!V?b!v-kZ2HEp&4*DU=C#gK}i!;B7USOgGwrRze z*Hv3CO$YfTh*^0u8&aXlrU@2Tc@KC%^d4@S%Do^^@`ytDCr|1J+>%UTMese!7St+D zUtHQ0?pnij8QdcY9Zquh^Alw#a>KL?L6KTVK}d9VfsZm(vL7U3f;yc!e>N1CyR8BP zWPUdA-Sqv8diCiv<5^XHz(4t^LI7uosR9W)DJs;lPa5ikU4Cnw=jC`M%^uMZl-UVko}l51Go^u8Aby zd8C0nXZkwo+<`I)?1zBb9@V#c}aSo zMp%+Ddh(`(v20jH1BCAC1xQ**=!6|kqE}@zwU4X!o*Q{1@EedQ9-jyY*;z*1rs-mn zci-rAc5;_Mbcax!vKzVj0Ix&v1+(i5iF`NhrGi^P6~alZgVdDI)cV|Y)E5)!p^349 zH{Q}9pi25e7Z20p*5JfZpA_m6l!G|^D(R798{f^8`7&z@Pq41`t)2BbkKt3;j>q{5 zoP}=JuL{f8e2vyW)#m2*Kq~Rux5<!Z_4mj6Ob!Jq-uK3*^t<@V{Q0$vzlk+3i3i*~I5tgG|mlfXLJ}#>=TfJvAE8hHkn>$skwF|?vCVU!X8;gUINV`(m2~Tev ztT8ysjByI7Ww%lQfdU_0mJyF{epE6Wf&lqbw1D60?dM*(U4Kfs!C#7cbQ%(MTm7*` zukEJFC$(G|Ab?smFLg?1XhxlIzPZ~+L`Fa8di-dR&yWEepVg8Y6YBsoOI&f~j|pJF z3%vL?Hm46oiJkgGt!|nW_ALt1r6C@i36J+VVke&QoAasMX|v8CGVRX=am1&Om&<=_ z?Gq%BE7j&A9R%Z^jM#e-qQX98pg;J5={5Q$=XIezgZv0$+5S?@-lGu7K>1mu{kNqy z58w#r-D{a}kbx<%?c?~K3qZkiDD(Kig**LdscF0rCxbI9cWbP=l=7D1cw|NIYEJXY zuY8LO+d>g~pr!U|@!RLxyb=%ob5l*p6}cKD*4S4lhwK^U${CuCi|YYiNI>UpHTfoJ zs$gZZ=qD+x-})F+I>>J}s!wfQ|6G5Y@%Cx_8br#?JZq^Vf3&{pl36UpUX+o`ch18f zum|~1Y%6$zszAZZf*(T41UWpQb{qlu4?#}>wfJr`U4t&y(m2ySok8KQV5+ygU_HkC z!;{fB7K4gHN*+kNoI>S&+nU;1aPC8l`xq4IpQFA^<5{mMcx0dY>av?Avt6#pox(lj zdEdEvAPWV29;}0O43mj>)~O;;9%S5$dmi<9kVfPCic%uq1D*u_79*E!4X^MF5)QwQD53@)0|eAf#A z=XD?L`vov_=jGg<%(?KtF1yz-y6NmG)cnvQL8iHA2ny9VbK9#oN|xK|4^A&YC1p*Y%j}Nvk0&B{q{l8T!@3SI&Yh+F4-?Z z7B!hNT2!7U_S+Rp?cLTnkAX24ytr=(jCkQ}<3$msqd#6`;aEwX;PnMXg0xo;CRo%@ zv!5*b2xZtqeUEM&&%biqEUrwkpXXkJSkCQu%2g1{JbW(Td{DVt*?5aLPA#}Uj={Op zbmq`K5!$z6?fNfU^%In6`O%zj$A}xvu&v1Ptzl1i#jcatwgxC4W~+VpK!n+OC{m?* zSVYV8uECQ>h=*^2U=Qbx%^cG;O@{CRv7Rd5uRa0_4W?C6|yJs-4(NO{TanPAc-Fhv>^C%}DbNlZVb#^WQibDJP-nUVRn_@7t>1 zP+*X`6({@v6&7$qHjae_KA0Pre_O&1YVp1gGUEd?WH$-#HlzDs@2wc=^cJr}P|*RP z_roT+G^^br%Me~}YX8ZY9e8L9d199bLEQ{=4Noe~rANGVuZySkbpNiCRb!hAz}dS` zux4ZAHXUiPp7m{5aV6;+YO5t9pd}Ia4hqdXU3n3I@nRi=$n~JCJn6eR-1QxQh**E! z*CjT1@)~i_;^m81>jCuSWPAcPT%Pf zd&tCiw%{jJSO7}lkOE}q?7z~LF;R6OxLjQpzuYu%!!>d$u|-ZQn2qbu3Cvho-0bA9 zt-$Cq|NNrytEn4=h9+b4Rm*L^COcRh5;o-^a_Gyq(9uTqdzyI{&$7(1&BFPHQE4A! z!dT;li0>kB3J)$Fq?#*1TN?-l0ag0g&lG)xvZA-dRMSQW!1E03ttERvO>@tnn+bq2 zJG}i5RVsK5gz9Bwd#E2*PWn(mf5+(Ji3&T&Y%HFxlytx-v_Nr*+P%=6-NAFt|s{?Y}!qi7jWPh-etf%UTw$SY;t(;@axqx}TN8hk1pFV($(y4c;>%I*VDi zB<=nE+gBqqPyA-S@k&swCiMi>MBs*_{ZQH2caK%IgDCtGZyH|3CaH$$&0i}_;QC#u zehKp`rJhOehTB2rM`wQvcI&q6;$G+=^9(U{ojN;bsn&tpxIZla$M@*O^dC&;z|=y! z&GJCQa)f)Qc`L<>h8`cB?%RA{O#MwcCVPm-bV{|q0? z8kyZyGG~v4uTq|tKQNG$Yz4*hoJZ!f)t_-PYa%Q%v?dR!der55wX+OX8-2L@dhF-B z81LU8%s3_NTCU5W?Yyne?Em$tM1RKim2gpk579u zr6;8xl|d#i zOAqNM*Pnbr5bHb!`(=1$1}d*e>m@&}$A?gipLk-1UhN5`TpKtwz1L0nTC&v7nzGTi z_w@_I6M3xA6MOO+-EJXDAZDJ-#A9`od)3K}KdDmpY00f$e-%YVMrH|K9ydsBRKw>O zuB8VH`dLh~%tQ?4Dsxm0gZj=F*K#F@;{)RlMi|-ZO(cTHG5tOrUB;%!?s2TvsjkT_ zS*xlIWGzqjU8kr>q10}(lxHSl3rPu`apw z*}DZ13+9Y^MYjrO7M4Trc~$+c8#B*(I|)^upP^4gYnZtZ7j$~~AC1%mU7PvssFEy0 zTqfY%lIZlpY|7@6(iUq#ni!Y>%zA#7D>P6s&~ncUp{K^pT7<`5APg%^a~)Li;KTc4 zb5~HfOXX)&mIiEd9!;ZC(dLy3yGFx(U@EhN7eR9UuVi`2r9IN`aydv5t2!V?K;nzF zzrceDpLU*Lg>fAIQj$E9TS0ah9pylC}bDlRrdfMjG$_gx4QdfEjW$l-z^Q7Uyx zt99ajpSnXlh0{AqVM(5aAY!}!Wvy5v%3RJXav4A&5xetZV7X(}8U7zZq&!|6|0w%v zgrsFMKj|>3k$fQiv&tA^jX2aot@SU-byb#p{PX&q3%lp3ZmkKNHZ1RDM3UcjX%F*7 z5!DroB|CKzy{}4ah>~@b58U&QLy&FpyI3wKdKXR)#8k}}(myg;%4n}A8T+p_Z8usL zG@2rSe8K@k|4I_t3!4(*po7PL^6F;Gq1}2i7INe^$UQVu;L+}%m2w=Y&{(NG!2D_G zdJ;}iN9n~Hl#A|c;qstevh3{TO3%Y)EQEFvL$WWfI_Y>m5iM3K~rfAW? zBU1!&nKgWCJ0kv;%MSJ zO4I0+UjI8my8h*M9`LZ$5{{bQPAo4ZBGMcn4bE0&T1qCOR$v$`bhgUVGAs=*k)2<=>=f?z4saH2N=&jkgti?QE5rfT`&g#Alza|^25RB2&2xim$l7l`B#9Uo_~j}}%CL{kSw(}l8O4vI&-cu|Ubc|) zRPi2ebfaN6zYAZ_rC>HAOT;(64->a&I&`n$_A@dTgh*A3mh`=Od-2|zfQ|6Fq3*#G zhh91-zI*X~KvqgB7Gz3xc_d9GotkMVWwc_$-Fb30BBrXd&;pq#*i#Hyyrfv+HIpt= zSMB!A+etE_?JAb8dQVQdUo}~&^JTY+7c3uqekj?@)Zshthhw#8aU{Nih$>wLXmkza*&vr9bvb8ZabZJfFg+SYPMac^~Uw0Ec7M2sgxYuND%`fQ6dxx$wD zijP${{KI4}>r?>%0#m}pzgD0*X)%ECWpBbq50BuMWY;yQI-)$w$h(WtM+9Z^mS1|BcwQWajply;6gmaAzRB*N1a>fA)2a~9 zb2ijGA?0TGs_8HR?a^KcRZ$8P>5?$E@Dbgl_7n=vpkJbG7f@yIMDUBK#CmsUHI&y zs#&=`F!{*pNsufYz9JRFW_Ea>F|$OukKNZ#qO7GggA72M>?0HVu=s^d&J8lK?2Au~Pg?PpnX{xl9z&2D zl3^b27M=y-GMC!L5krSGA4GZ2&c+U$@$BIi2wL;^pA+x6SG4IxzE?o$ktF)bZ4kny zUck1@aC1;Th<$gClp5^-iuH2j$4RijQe18Zd!Ap915rglop@M-6_7t+K9)V(JvaxF z<1+=<*2h(lfTuJiC-oFu$*^5;u4Sv~R~J_@X83>Fs%8!cCEM&tItzEkk1%ECHE*0I zdqr#j0-dx}+1rb}0tgvE^|LbnkQ~nd|Dnd>s!Rx7ma8&w-i?t*&Pa~$VSL)a>yZhJ ziEP2UU!L4+-_%zbdod--m-w0{)*hWMix`o-g}X*aUN9hIeDVkuRpT$Q*OMG+@U7mT zIK3OVx&m%u8z171axa~EvIdanD?_n?Z)H=iY~&~!swgSQoT+zrOw3Ms(s?MOKNQy` zu$ZxttK$%3Su_KQ)1^}Z=K~ZBhxg*;4#e#VfAHh^zC#@V0@r85bd9FTtVFFJ4K-~| z(ZxMsRgFBA_kfx{g<^m|Zb2c&5P1&)#{C2!&1}9Kew@?Kl$|TzaiUpR&i!E-U+)OU z(U)FK;RYj$^u;}A=J+$|i0>Ae*4PE7?Y4vBj!@eR;Vg;u!xqd*4#cWT&&XDeee%b2 zRLJOM&2_ogrA)1EtbNZ&RnF;McXr`(-2jmd3X;~$ zw>=+Pm_^0BMpw+=bT6x)=TSpgzkhp8TUMz0Q9HJUo924zm(oVdnnXb2NmqAcS~9G- z(NLNF5})ERmQ%I@uFA7BiR`*ve2dJ!vG9?qTZ{MdylwXP>#g8)EqXCm?5n0*+=z4! zyY2StZMUBn6MU{@{Q03)VM;VOzJv^01e=)7W}p!jg5D!F&bl|fo^T+;m~6(23n@96;?a!(SKeZAq& zU0dX@sgyC+A)FQs7Zj@en(&uXpG1MBEa^KUX`IbM@671gtLHlFwqW5Wz2UZ!x}Apg zeXnkkB_YpncTQ}c?*iiD$TY<<*^!P-^)PRMy>D70(#h@eg*@Qvgk~{rU2J zBf4}r%ra1TiG$c$F)7@WvHIbNg4=Yu0e86fE&RZVuTpw@RThI$14m^0+PZNcnGK6$ z4sl!*u76x{4jGYX=c=3Z^@8KkmR%^QbbTRQ-r)4iBvVF9qI5n}_E_t5qD%C+k7&;Z zgf!)ffX!LKRe7ld=mH6VhKGy&-LIur>7%KBhZ>9qOL7h@6h1y8=YaRE2 ze=bF3_$w+Zng;2RYMkbj=@qs=sTGp0%BonKj>#W-IPt30VGa5ZmS2~|5l-`aBr{r% z`EsTM(dXSF?kDFvAxm#vtg={}t>S+=#RvDLXE1L3DdE>iqR+Q1{_)`BP~6%|)*=DN z4NpXO!ihs0+J~QG9L*!^Xx%^c$JxZwEb7Kqz=}YHi#)Ha!2>|DSz;Ez2ctyVk~ z&LMhp5jBxCQgzeM>81_2)OEh^BjaPIQ_~OelC3J@M`{~$hMXwrve-c9j!6=I$+4q| zNHw23@3w`vFG>L9R*vUWH9z-V%OWoo(bW=1#fT$$pXM%c%#a9U>N)Yts_ zON{T4`USYpa9i3+pZ8P&q$_AksV^E@R-7cYtY6CfK&(%m-iTjj(^;~RJGLd$-cjgf zZsdFP;w5i$g`wCO+~99(j>6MKO=@q6D=j+g~vscvaL+X{Q%LM|Ot@oHCwUTEmkE*XwF(LQy@M$|{m`^8vYc+SH9Ob@&GC&Iha z;zzl73n}ckk5qoqE3JGQyqq=(SZxT7X^ta|(RFecdS;i|3f23299@+eXtk55xucDO ztABSb8#E%!htIXM5_|dgW_>(WEvVf;v??OFNcSaE`WT!3zHQD0L0~ni%GQ4KOZI%j zxlUTx|7scC3nBJcCVG;}G8S7e>K+#RHI(5x7H$1rFCDK@jgst*1b=2;LzrS1`J0Sh{4A zCVAZci#&gT`T_{GVV`zoyJ%9J<5|s6oTNAqA@P{cvHosRuj}_A)s1*X*$?zBnahBI zs2B}@4B_t%Mq@wSlc;UIO8e$t5xsA98CiD~@pWCEV^V#<6TtWkoWS5)QDw(|wf5LM0n}6>ee}<1bfM#xTJGvXXCs+95FQqtB{9UvC{&jgR1u+dhFIjpd?g=_Tp9A3v^d z{}|x#m(!6UO&la83Q5<^&Tj9U4FBtI#eaAss z=hpM9@d|=FO?9gQ@FETb2kYZNM9_sJEG$Az(!eOhv(IgWZ6nJJ3~n(%@@#$DjdJpP&RS{Md) zn%V=~Das77AkS<2w23f~S(KG+QtI?DgNJoB68ALe5m2?a00Rbwa!~L0?|JR*?f5%T zhy>f{U6Awzf5Y@NIwIoi&d)O2eji{%+9h&bjbxJt5nHJfRoN+i=Mh`-g+cY+-ysS> zhvK-B$a(qbn{Hii&fV&0YXjxohlYl>_CyAT|Nhc=q=p``iB0!t* z|8Y{1Y~kuH52?9D+^HS@(Es&;C!xOhVp$hfiw_^PHGrw~gJi><0lfTojxd->UcUS$ z@ux^m|0h5=t(EH6Gs5;_d#r?!|3*Rj{rmTkDxca#at!PMA~p%VhjuX_rTN>k9rkw6 zeQ?l~xsA!s6)-^ijZYN*)~Ah)-|jKt_!yGR?xasi0ekmcWn{x1|nFt5lIGJP%n176Vlj0&z{$D%QOhmg*YF#V2($u z-y$!~07v>U5fDQlYTsA7!yBe*7{zzuM5*%5ijRHpc_MHq z$7pG3>En_?u>Ite1-+c2m6Tstr2q!a;BRv;+dDZqDgM0+a5VDWP*+zE-Qecs)iySD z)@0S-<)=_a`{b6~Kz4OqE(&q<@ ztIFC0WYA;Qjz8Dt9KV9NHk@(Q{P7{&jp0Ru?elXI*>=QhfWBt;B^|Zn3W{>9-JpiI zJ25!~G#izjk1o$!ow_pn6cBIxT>xp!_0j*^+dl%_CX>Kuf`O1a7*98NY6mFjO34J7 zlgCl%T1PQmI+~Gmtn)AkKfHyD$F7gMHJ*#lWYdv5-EIq}?xZA=dK-Wgf0u5a{0?sv zgK&z0E(;M%s2+fznSf-;IWDs2WjIg@*R-5ubgo1i4?q(pjBT49o*`|2{l34ibhFkr zL3Y5?YIt_^5*d)hhjk%b9v^>(N1V3{(gA#y31a0F8N-G@RtLe34{>L0RYqW7zz6+Z z4!S9T3hmS{uyaUA3j`@BDkgscovrPtf-HGpcw2Jw(wnXdrsdV0m5TPQx& zrAefY23E+h>w09GZH1PT99YG2N-nbfZU!c0F8}SwH?F_K!I0-pij>XHx)m>IlL59d z6bRH1+8N-J|E9Nz^Yby{`Y&Q8AVv|ic>|_%RDi{=@28XdeHzMa!|8CnSO?IO49Il z0Xn#pSt9j{-7>Ynd1(Ret~roLpg0o9;{Uf<|--kWa(i|6o8Q+-9_;=+(#^=qTJ>sXN+& zAN;;9d z;hUabdb(FlgO~hWinJ5Yg-^|XYBleNx9!JyPQm&(S2n46#iT|Q+h`c=^mFsy>UP-I zsipK9jWV(I5l4=2&fjb+O0W3VddxyyGz(JcUy-S+JbR*ZSYt21Yq6!1M0U!1EscVz zahOpa;hbF|;hl*Y$UPjb*Dq?8$87!YeLDgJ6HxAk~rE0Wg`(usJ zrtc=Ax(~0~Vd*|~KA3U^y?p||9dr$@12ENR1WfB5H8mHn;C7xd6PvrSzB@TD@iJFwH-t2p59RPc93 zGM6wKp4i8y9NE=KI@r`+wd;Jc^FV7J;dsa|8>%@I++BM3Y|!!%ezgKI3p_byz{jMa zwIyX-A=ciapsJK)9iw6pb<)+^0}nQo&4SKcW^&wt~a^Y^*iT?+X0^KWsw%XfvWzBe*H(?!0Bt}C-o zk``&$tS3x^zpWz_|DVsUrKFjTTevtk@L^+gRJ04ws`ybG07 z8cy-wYtpkK<(oFG77&Z7G`dCuV&x3n{R)W;AMus+nvtdyq?^Xrx$EXP8~;gqhc~XQ zE-gP6)0=L||G~tp+*N4Fj6jAb`!^Qe4uXAm7FrrFEf?RDgTLhyC?l%J*l?t~+Tu&& zHJb+Cp~ho`ZW=kCjnW(F5qlxkLFt~`7vZi?j-7!OlYwCL|LaOu08cUvUrtn$o>xjI z-l^)7!K}pL$@9~cuPSAg89Xr!%spB2bskuySC!FK2FD}bPYta;VB}c z^>~wP_&Z8|TX9Lf6SYdN@4_b#2jy0G+&B|(G>WKUdjPFPIgnp${maX5e|Vx1h1lQL z8J4uly=&TrIFEvs5u)#>*F^Kgj?wbu!fVRy;V#-$%stug+nY1vj2^n^MohI|B6)SQ z>-$v9@~<2M!H4E8@?R)d`+%dUUv@#BhL642GO72PK`4S(q23j(IXT~#ucR%F4<3bj z2G&PcowBTS$C&eQ-@1~DP|!o8N|f*L@FsH?%PD&<>cKvU-Cu99*lfIiO%}V^ls}nX z*dHg{6%Jg4yegh+1xMrx1j(T^zU#3R+KP(YoHF_vqO<)x5fE-@=Mh?gTnwSmxeF_f z@AuOzK-c1kAUTNIc&LtMc{S58pWn>iF~hXX2xf!PBVs8)%w-6I+|ul*&~K}^sq>`@ z7Kbq9Trdp__m0vYcVRG-jZH}8P+dg6kxIH&tXNMvmR@7vxRFNCI%ym)4tEDaqel>2 z0>s>lOx^;TdL+0g1i=e-1Wwc7gSGB9$UXk=3I6)|&78yyPLnBTHj|Zl$pZI`@6rki z=aa7y!)7P{i#Ib0Dwsr{FfJ+7WG9jN^9cX-8s`%e_Ymz&%|dg4ua&t@PN1H!@|F3& zaOWk1`&h}jvToU(eW)vA#s9j1oZD+3YeO39v$XI~vGS0IKBajc)deGyL ze-KlQaQAcIReVY095xBGU%7phQ|Iz5MAtg;(*T2`kL)GSi!l zu;uA<6OxeUjW#cFB2cpBd=zt()=a1jEz+s^3C`|HBU48$NzeApkaGx2Y1>;m*f7CAM;Evxuo7{ugOCev4f4>XSwuuf zqxK!*$Q*j;oAY`50(CT@oanQ1j%U^swVnsuZX~doPpK848Nu#^CwuE|{S&-A zrK_lTwE(SQ8hjUNa3&9B)TLgy#3S7{GW-pTo9aabG6<{-=F zDNqYuW=HdjKB01cN3+Mbxp$e37hhld2KFitsko(bskZ*p;NZ3(ZrB!ZYe3rY0dK<~ z7zhP_x2>3^b^X&IF~?8ifzo!HDK7QjO~n6MdvJR#jc%gV}P{~nPfBPS=9x_u`G zxPy(2FhGaJAaB2#ndYh+a7^0*J88!p@$N4DXAq$mOT0WW~ZqT`QY|frE|f$!NI|7_!^MqGl7aYt zm3E#)B29){tXrCVe4sX$vTgYV__-`rS646Xv@H$Fn33%V{iw<0G*IDX+f!_NplKc? zV`Zsq;8e5$kHGEiN4G`)LnKna0NSErkV*w2(R_tdoW316P(J;sf$vtl+0%P#W3t%4 zeQcr44Vb%NJN=~ezx9)Bt?gIfzqWHGPXZ6bvF&pfKh3@0`Kro)0$6JRioXt#6Fz@_ zDGqpk-g1DKLw}u77}~M5xP~U4J$p8_V`HfDcY{zb@CpW80KzDG{Ocwv6}TJDyoM=N z&Np4afq;h{SSt;9I=5$0Ko!6-^!Mt)ZT(rKu#?|{&a z_^QRMVzBG(;1fK6+IahdfIwEEbaSuKI9p(<>u46dV@i36=$ZWSBV^la!oa}u&m0R- z2Zr&Of!+(feAaXv(>twhHvol9f)FKkg>CRAAQ#jE*}k#UStxQKNDq`)u&7|!o!kFf z9mOis4}x6VM7Gax1LRrT->UCyw|dKI;f6o;%G*=GiR%kkqA!Dk!(((vO55i_bnDyS z82UT-uBS*^N3rP>HJOv@L$j@{tZV@|vwJHVih)6w_um_SzKvAguz!YMy=S8@EFG-< z9T-i{<&Y0Jyo(753jS>0GAH(Lb5!F5z=&pz>B9HwFWksY=M2o9-i3Rmp2d2J7c3g6 z*xbOi)t$c2Irr~D<&CHDo+;9HMy$HOplE_mM7p+YU|!6gtZ4yLEKLFz+F+UC?$8$}UNTTEgQHCB{oY>&s_kujh>@=oZ{B;OE?`Nf zbi!CGO&meUaD4aky2h!Z#y z-`iw$Jv;=Agtc^noJ!OeKOy@vd+iKUXAmh(8t=48uKG)<=T3v7Jam*B&@xP>p zKH;|B!@q8>m;W7ipZ_KlZIfXNx}nxRMEMD@)7da<8~oI`j8^uUjRiy7h8UPoQ=Hur zK&?z68x7LZsn~_d&>b5`0_!~Q0~1%q$}O9=%K$WiR?BZD>CB5KlTB9}i8hh>;$rYx z0QD4~FwO_RRtqV=9TO`Dz|UPgc^{Tu7-o00YnZm8l&}qU7YJJtpEKN6_X=0VTrN;(tCf76Q=8GG7DV zqo(7owN0U~tRG(;XUQ*~G zWned+?jl#HN!EMBii7GV6s|(nmZ)VgBo>%TqlVI}0H#`RvGTL(N%9Ban?`{Hz4BT? z)RIEeaqJ2cQpb)4!Ao%WfwSDhBAga-=5!(EMafvbSU9ycaHfA5UH;`t)&Q zXBz>unV-<=>1vwNbz?k_5SIYPZz|0wUR)-!q>Bu~fpC1-gS8Y0n3F(P)s~h&(aX0& zT0Ca1;BtoRF(Y64()ODMpI;xw_Q@8;_GVA0gVCz;lzT#>Y(y;h836z*bq~M)0_;=C z?E*0~JlVgeobjqDSo;8ZC@E?vTT0g);3L59B7nlz9_eQfSTC*)L0EzrJW< z{4V^&UA;=Vn^(j?j19Yd$j~oeIXshes>B-T5otrj!{q1PClc%+Fc08y z+Rxw`(v}r%e&XyZ24`-@H@J*d=XWWiTc)xA{`m7**Z0k_uV%sXqh*zS76+$Uo}zfX z__cpX_ipG$ZeX;*IZ@o*UCfvg?Ro$s(jK~i3Om?&Oo>{jK%Q&g0UDDQ**iWp0&%`xzhw*($W!n`4-g1mZ%qoE9z7L;h+?Bm?VhCkrQYBgM=bnap7@6 z%Zdj6ki7wQ*n@8)Q}<4Yv+JN?h|ENsYy+W;IHC|+c$Y0jigCbPQn+=*1S}79?FhB9 z>pliT#`IX~du8^28Mq%VY=Ky@VpfdK{SLVbgu6DE#UWurr1yCk=I6h*b9ab0_j_pl z3H#DzdUXiLhUwwMk~NSb06a4ZQZ}Gw7(oK3j)qV{(0?iE#7<2*PEVZ~t^BXB82u}} zmk{z9TxP(FB=PA15KTJcj1#9l&X+~7>3G+})+Aar)G5=L@}mp4iI>snt2Bx)(R(Sp z&Z5aMV!Y%27W6frI8l3M>ApRVZG~=6mc7Pu_^>H*?I#`a^u9Am!-YoeJc6Nqk!C}k z)lD_kh;lRwwyCXJB49fb3#E(v_oJw6J-bacUW^cZhTLgfyIi$m#Pe=J_kd@Ff-kxe0(3IN^j;q`<#FZ-2LBl}Hr39!fT+Of7quAc(fp~*&=vwId$1VT)BG1LjGD3O^+ z$9zJZFyGN$qx?t!oCC*OXeo1jch zqkw46G~cRntw!8m6Pdevnl|^|=t-GDC7l|81%Z=DP# zCg_wqXaexT)QA(&M#gErDw|QVD Date: Thu, 14 May 2020 09:31:04 +0200 Subject: [PATCH 49/73] Address comments --- .../src/catalog/CatalogLogic.test.ts | 4 ++-- .../catalog-backend/src/catalog/CatalogLogic.ts | 16 ++++++++++++---- .../src/catalog/DatabaseCatalog.test.ts | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts b/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts index f8e455e1de..38f1088b98 100644 --- a/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts +++ b/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts @@ -28,7 +28,7 @@ describe('CatalogLogic', () => { it('works with no locations added', async () => { const catalog = ({ addOrUpdateComponent: jest.fn(), - locations: jest.fn(() => []), + locations: jest.fn().mockResolvedValue([]), } as unknown) as Catalog; const locationReader = jest.fn(); @@ -41,7 +41,7 @@ describe('CatalogLogic', () => { it('can update a single location', async () => { const catalog = ({ addOrUpdateComponent: jest.fn(), - locations: jest.fn(() => [ + locations: jest.fn().mockResolvedValue([ { id: '123', type: 'some', diff --git a/plugins/catalog-backend/src/catalog/CatalogLogic.ts b/plugins/catalog-backend/src/catalog/CatalogLogic.ts index d8c4804074..ed195386d0 100644 --- a/plugins/catalog-backend/src/catalog/CatalogLogic.ts +++ b/plugins/catalog-backend/src/catalog/CatalogLogic.ts @@ -24,19 +24,27 @@ export class CatalogLogic { reader: LocationReader, logger: Logger, ): () => void { + let cancel: () => void; let cancelled = false; + const cancellationPromise = new Promise((resolve) => { + cancel = () => { + resolve(); + cancelled = true; + }; + }); const startRefresh = async () => { while (!cancelled) { await CatalogLogic.refreshLocations(catalog, reader, logger); - await new Promise((resolve) => setTimeout(resolve, 10000)); + await Promise.race([ + new Promise((resolve) => setTimeout(resolve, 10000)), + cancellationPromise, + ]); } }; startRefresh(); - return () => { - cancelled = true; - }; + return cancel!; } public static async refreshLocations( diff --git a/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts b/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts index 80a0377445..7a474246d9 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts @@ -53,7 +53,7 @@ describe('DatabaseCatalog', () => { await catalog.removeLocation(locations[0].id); await expect(catalog.locations()).resolves.toEqual([]); - await expect(catalog.location(locations[0].id)).rejects.toThrowError( + await expect(catalog.location(locations[0].id)).rejects.toThrow( /Found no location/, ); }); From 685c962a947b3ace63bfbd9937d11ee5a78091f9 Mon Sep 17 00:00:00 2001 From: Victor Viale Date: Thu, 14 May 2020 09:38:43 +0200 Subject: [PATCH 50/73] Use the /contribute URL for the Good First Issues link (#858) GitHub proposes a `/contribute` URL as a page to get people into contributing, which declutters the usual issues page a bit. Issuing this PR as a suggestion, feel free to close it. Example: https://github.com/spotify/backstage/contribute --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5b863cfa6..d2a0bbd3a9 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ We would love your help in building Backstage! See [CONTRIBUTING](CONTRIBUTING.m ## Community - [Discord chatroom](https://discord.gg/MUpMjP2) - Get support or discuss the project -- [Good First Issues](https://github.com/spotify/backstage/labels/good%20first%20issue) - Start here if you want to contribute +- [Good First Issues](https://github.com/spotify/backstage/contribute) - Start here if you want to contribute - [RFCs](https://github.com/spotify/backstage/labels/rfc) - Help shape the technical direction - [FAQ](docs/FAQ.md) - Frequently Asked Questions - [Code of Conduct](CODE_OF_CONDUCT.md) - This is how we roll From 150908ae5500992b441e3e6159f4c5e8a3cc0cfc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 00:12:23 +0200 Subject: [PATCH 51/73] packages/core: add common Observable type based on TC39 --- packages/core/src/api/index.ts | 1 + packages/core/src/api/types.ts | 63 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 packages/core/src/api/types.ts diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts index 0f111b26ba..2eabc38280 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -18,3 +18,4 @@ export * from './apis'; export * from './app'; export * from './navTargets'; export * from './plugin'; +export * from './types'; diff --git a/packages/core/src/api/types.ts b/packages/core/src/api/types.ts new file mode 100644 index 0000000000..28a6155017 --- /dev/null +++ b/packages/core/src/api/types.ts @@ -0,0 +1,63 @@ +/* + * 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. + */ + +/** + * This file contains non-react related core types used throught Backstage. + */ + +/** + * Observer interface for consuming an Observer, see TC39. + */ +export type Observer = { + next?(value: T): void; + error?(error: Error): void; + complete?(): void; +}; + +/** + * Subscription returned when subscribing to an Observable, see TC39. + */ +export type Subscription = { + /** + * CAncels the subscription + */ + unsubscribe(): void; + + /** + * Value indicating whether the subscription is closed. + */ + readonly closed: Boolean; +}; + +/** + * Observable sequence of values and errors, see TC39. + * + * https://github.com/tc39/proposal-observable + * + * This is used as a common return type for observable values and can be created + * using many different observable implementations, such as zen-observable or RxJS 5. + */ +export type Observable = { + /** + * Subscribes to this observable to start receiving new values. + */ + subscribe(observer: Observer): Subscription; + subscribe( + onNext: (value: T) => void, + onError?: (error: Error) => void, + onComplete?: () => void, + ): Subscription; +}; From 4cd23d0231dfabbc7c00360db14e12ddb14bdf6e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 00:22:40 +0200 Subject: [PATCH 52/73] packages/core: added oauthRequest API definition --- .../core/src/api/apis/definitions/index.ts | 1 + .../src/api/apis/definitions/oauthrequest.ts | 173 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 packages/core/src/api/apis/definitions/oauthrequest.ts diff --git a/packages/core/src/api/apis/definitions/index.ts b/packages/core/src/api/apis/definitions/index.ts index ca1290403f..5d0b8095d8 100644 --- a/packages/core/src/api/apis/definitions/index.ts +++ b/packages/core/src/api/apis/definitions/index.ts @@ -23,3 +23,4 @@ export * from './alert'; export * from './error'; export * from './featureFlags'; +export * from './oauthrequest'; diff --git a/packages/core/src/api/apis/definitions/oauthrequest.ts b/packages/core/src/api/apis/definitions/oauthrequest.ts new file mode 100644 index 0000000000..b31b828183 --- /dev/null +++ b/packages/core/src/api/apis/definitions/oauthrequest.ts @@ -0,0 +1,173 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IconComponent } from '../../../icons'; +import { Observable } from '../../types'; +import { ApiRef } from '../ApiRef'; + +export type OAuthScopes = { + extend(scopes: OAuthScopeLike): OAuthScopes; + hasScopes(scopes: OAuthScopeLike): boolean; + toSet(): Set; + toString(): string; +}; + +export type OAuthScopeLike = + | string /** Space separated scope strings */ + | string[] /** Array of individual scope strings */ + | OAuthScopes; + +/** + * Options used to open a login popup. + */ +export type LoginPopupOptions = { + /** + * The URL that the auth popup should point to + */ + url: string; + + /** + * The name of the popup, as in second argument to window.open + */ + name: string; + + /** + * The origin of the final popup page that will post a message to this window. + */ + origin: string; + + /** + * The width of the popup in pixels, defaults to 500 + */ + width?: number; + + /** + * The height of the popup in pixels, defaults to 700 + */ + height?: number; +}; + +/** + * Information about the auth provider that we're requesting a login towards. + * + * This should be show to the user so that they can be informed about what login is being requested + * before a popup is shown. + */ +export type AuthProviderInfo = { + /** + * Title for the auth provider, for example "GitHub" + */ + title: string; + + /** + * Icon for the auth provider. + */ + icon: IconComponent; +}; + +/** + * Describes how to handle auth requests. Both how to show them to the user, and what to do when + * the user accesses the auth request. + */ +export type AuthRequesterOptions = { + /** + * Information about the auth provider, which will be forwarded to auth requests. + */ + info: AuthProviderInfo; + + /** + * Implementation of the auth flow, which will be called synchronously when + * triggerAuth() is called on an auth requests. + */ + authHandler(scope: OAuthScopes): Promise; +}; + +/** + * Function used to trigger new auth requests for a set of scopes. + * + * The returned promise will resolve to the same value returned by the authHandler in the + * AuthRequesterOptions. Or rejected, if the request is rejected. + * + * This function can be called multiple times before the promise resolves. All calls + * will be merged into one request, and the scopes forwarded to the authHandler will be the + * union of all requested scopes. + */ +export type AuthRequester = (scope: OAuthScopes) => Promise; + +/** + * An auth request for a single auth provider. + */ +export type AuthRequest = { + /** + * Information about the auth provider, as given in the AuthRequesterOptions + */ + info: AuthProviderInfo; + + /** + * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". + */ + reject: () => void; + + /** + * Synchronously calls the auth handler with all scope currently in the request. + */ + triggerAuth(): Promise; +}; + +/** + * Provides helpers for implemented OAuth login flows within Backstage. + */ +export type OAuthRequestApi = { + /** + * Show a popup pointing to a URL that starts an OAuth flow. + * + * The redirect handler of the flow should use postMessage to communicate back + * to the app window. + */ + showLoginPopup(options: LoginPopupOptions): Promise; + + /** + * A utility for showing login popups or similar things, and merging together multiple requests for + * different scopes into one request that inclues all scopes. + * + * The passed in options provide information about the login provider, and how handle auth requests. + * + * The returned AuthRequester function is used to request login with new scopes. These requests + * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests + * triggers an auth flow. + * + * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. + */ + createAuthRequester(options: AuthRequesterOptions): AuthRequester; + + /** + * Subscribes as a handler of auth requests. The returned observable will emit all + * current active auth request, at most one for each created auth requester. + * + * Each request has its own info about the login provider, forwarded from the auth requester options. + * + * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. + * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". + * If a auth is triggered, and the auth handler resolves successfully, then all currently pending + * AuthRequester calls will resolve to the value returned by the authHandler call. + */ + handleAuthRequests(): Observable; +}; + +export const oauthRequestApiRef = new ApiRef({ + id: 'core.oauthrequest', + description: 'An API for implementing unified OAuth flows in Backstage', +}); From 348e35d60b8ce6ee07225957ea0a6c8ef910c16d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 00:35:06 +0200 Subject: [PATCH 53/73] packages/core: added zen-observable dependency --- packages/core/package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index df69a9353b..eaf25e07fd 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -41,7 +41,8 @@ "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-sparklines": "^1.7.0", - "react-syntax-highlighter": "^12.2.1" + "react-syntax-highlighter": "^12.2.1", + "zen-observable": "^0.8.15" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.5", @@ -55,7 +56,8 @@ "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/react-helmet": "^5.0.15", - "@types/react-sparklines": "^1.7.0" + "@types/react-sparklines": "^1.7.0", + "@types/zen-observable": "^0.8.0" }, "files": [ "dist" From 08732d57d0548d25e4ea358ed4af266478d37ffe Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 00:35:46 +0200 Subject: [PATCH 54/73] packages/core: add implementation of OAuthRequest API --- .../OAuthRequestManager/BasicOAuthScopes.ts | 73 +++++++ .../OAuthPendingRequests.test.ts | 97 ++++++++++ .../OAuthPendingRequests.ts | 111 +++++++++++ .../OAuthRequestManager.test.ts | 178 ++++++++++++++++++ .../OAuthRequestManager.ts | 158 ++++++++++++++++ .../OAuthRequestManager/index.ts | 17 ++ .../src/api/apis/implementations/index.ts | 1 + 7 files changed, 635 insertions(+) create mode 100644 packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts create mode 100644 packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.test.ts create mode 100644 packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts create mode 100644 packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.test.ts create mode 100644 packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts create mode 100644 packages/core/src/api/apis/implementations/OAuthRequestManager/index.ts diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts new file mode 100644 index 0000000000..053d017da8 --- /dev/null +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts @@ -0,0 +1,73 @@ +/* + * 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 { OAuthScopes, OAuthScopeLike } from '../../definitions/oauthrequest'; + +export class BasicOAuthScopes implements OAuthScopes { + static from(scopes: OAuthScopeLike, normalizer?: (scope: string) => string) { + const normalized = BasicOAuthScopes.asStrings(scopes, normalizer); + return new BasicOAuthScopes(new Set(normalized), normalizer); + } + + constructor( + private readonly scopes: Set, + private readonly normalizer?: (scope: string) => string, + ) {} + + extend(requestedScopes: OAuthScopeLike): BasicOAuthScopes { + const newScopes = new Set(this.scopes); + BasicOAuthScopes.asStrings(requestedScopes, this.normalizer).forEach((s) => + newScopes.add(s), + ); + return new BasicOAuthScopes(newScopes, this.normalizer); + } + + hasScopes(scopes: OAuthScopeLike): boolean { + return BasicOAuthScopes.asStrings(scopes, this.normalizer).every((s) => + this.scopes.has(s), + ); + } + + toSet(): Set { + return this.scopes; + } + + toString(): string { + return Array.from(this.scopes).join(' '); + } + + toJSON() { + return Array.from(this.scopes); + } + + static asStrings( + input: OAuthScopeLike, + normalizer?: (scope: string) => string, + ): string[] { + let scopeArray: string[]; + if (typeof input === 'string') { + scopeArray = input.split(/[,\s]/).filter(Boolean); + } else if (Array.isArray(input)) { + scopeArray = input; + } else { + scopeArray = Array.from(input.toSet()); + } + if (normalizer) { + scopeArray = scopeArray.map((x) => normalizer(x)); + } + return scopeArray; + } +} diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.test.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.test.ts new file mode 100644 index 0000000000..be762ca936 --- /dev/null +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.test.ts @@ -0,0 +1,97 @@ +/* + * 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 { wait } from '@testing-library/react'; +import { OAuthPendingRequests } from './OAuthPendingRequests'; +import { BasicOAuthScopes } from './BasicOAuthScopes'; + +describe('OAuthPendingRequests', () => { + it('notifies new observers about current state', async () => { + const target = new OAuthPendingRequests(); + const next = jest.fn(); + const error = jest.fn(); + + const input = BasicOAuthScopes.from('a b'); + target.pending().subscribe({ next, error }); + target.request(input); + + await wait(() => expect(next).toBeCalledTimes(2)); + expect(next.mock.calls[0][0].scopes).toBeUndefined(); + expect(next.mock.calls[1][0].scopes.toString()).toBe(input.toString()); + expect(error.mock.calls.length).toBe(0); + }); + + it('resolves requests and notifies observers', async () => { + const target = new OAuthPendingRequests(); + const next = jest.fn(); + const error = jest.fn(); + + const request1 = target.request(BasicOAuthScopes.from('a')); + const request2 = target.request(BasicOAuthScopes.from('a')); + target.pending().subscribe({ next, error }); + target.resolve(BasicOAuthScopes.from('a'), 'session1'); + target.resolve(BasicOAuthScopes.from('a'), 'session2'); + + await expect(request1).resolves.toBe('session1'); + await expect(request2).resolves.toBe('session1'); + expect(next).toBeCalledTimes(3); // once on subscription, twice on resolve + expect(error).toBeCalledTimes(0); + }); + + it('can resolve through the observable', async () => { + const target = new OAuthPendingRequests(); + const next = jest.fn((pendingRequest) => pendingRequest.resolve('done')); + const error = jest.fn(); + + const request1 = target.request(BasicOAuthScopes.from('a')); + target.pending().subscribe({ next, error }); + + await expect(request1).resolves.toBe('done'); + expect(next).toBeCalledTimes(2); // once with data on subscription, once empty after resolution + expect(error).toBeCalledTimes(0); + }); + + it('rejects requests and notifies observers only once', async () => { + const target = new OAuthPendingRequests(); + const next = jest.fn(); + const error = jest.fn(); + const rejection = new Error('eek'); + + const request1 = target.request(BasicOAuthScopes.from('a')); + const request2 = target.request(BasicOAuthScopes.from('a')); + target.pending().subscribe({ next, error }); + target.reject(rejection); + target.resolve(BasicOAuthScopes.from('a'), 'session'); + + await expect(request1).rejects.toBe(rejection); + await expect(request2).rejects.toBe(rejection); + expect(next).toBeCalledTimes(3); // once on subscription, once or reject, once on resolve + expect(error).toBeCalledTimes(0); + }); + + it('can reject through the observable', async () => { + const target = new OAuthPendingRequests(); + const rejection = new Error('nope'); + const next = jest.fn((pendingRequest) => pendingRequest.reject(rejection)); + const error = jest.fn(); + + const request1 = target.request(BasicOAuthScopes.from('a')); + target.pending().subscribe({ next, error }); + + await expect(request1).rejects.toBe(rejection); + expect(next).toBeCalledTimes(2); + }); +}); diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts new file mode 100644 index 0000000000..2398282842 --- /dev/null +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts @@ -0,0 +1,111 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Observable from 'zen-observable'; +import { OAuthScopes } from '../../definitions/oauthrequest'; + +type RequestQueueEntry = { + scopes: OAuthScopes; + resolve: (value?: ResultType | PromiseLike | undefined) => void; + reject: (reason: Error) => void; +}; + +export type PendingRequest = { + scopes: OAuthScopes | undefined; + resolve: (value: ResultType) => void; + reject: (reason: Error) => void; +}; + +export type OAuthPendingRequestsApi = { + request(scopes: OAuthScopes): Promise; + resolve(scopes: OAuthScopes, result: ResultType): void; + reject(error: Error): void; + pending(): Observable>; +}; + +export class OAuthPendingRequests + implements OAuthPendingRequestsApi { + private requests: RequestQueueEntry[] = []; + private listeners: ZenObservable.SubscriptionObserver< + PendingRequest + >[] = []; + + request(scopes: OAuthScopes): Promise { + return new Promise((resolve, reject) => { + this.requests.push({ scopes, resolve, reject }); + + const pending = this.getCurrentPending(); + this.listeners.forEach((listener) => listener.next(pending)); + }); + } + + resolve(scopes: OAuthScopes, result: ResultType): void { + this.requests = this.requests.filter((request) => { + if (scopes.hasScopes(request.scopes)) { + request.resolve(result); + return false; + } + return true; + }); + + const pending = this.getCurrentPending(); + this.listeners.forEach((listener) => listener.next(pending)); + } + + reject(error: Error) { + this.requests.forEach((request) => request.reject(error)); + this.requests = []; + + const pending = this.getCurrentPending(); + this.listeners.forEach((listener) => listener.next(pending)); + } + + pending(): Observable> { + return new Observable((subscriber) => { + this.listeners.push(subscriber); + subscriber.next(this.getCurrentPending()); + return () => { + this.listeners = this.listeners.filter((l) => l !== subscriber); + }; + }); + } + + private getCurrentPending(): PendingRequest { + const currentScopes = + this.requests.length === 0 + ? undefined + : this.requests + .slice(1) + .reduce( + (acc, current) => acc.extend(current.scopes), + this.requests[0].scopes, + ); + + return { + scopes: currentScopes, + resolve: (value: ResultType) => { + if (currentScopes) { + this.resolve(currentScopes, value); + } + }, + reject: (reason: Error) => { + if (currentScopes) { + this.reject(reason); + } + }, + }; + } +} diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.test.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.test.ts new file mode 100644 index 0000000000..940c0230f9 --- /dev/null +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.test.ts @@ -0,0 +1,178 @@ +/* + * 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 { OAuthRequestManager } from './OAuthRequestManager'; + +describe('OAuthApi login popup', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should show an auth popup', async () => { + const oauth = new OAuthRequestManager(); + + const popupMock = { closed: false }; + const openSpy = jest + .spyOn(window, 'open') + .mockReturnValue(popupMock as Window); + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + + const payloadPromise = oauth.showLoginPopup({ + url: + 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', + name: 'test-popup', + origin: 'my-origin', + }); + + expect(openSpy).toBeCalledTimes(1); + expect(openSpy.mock.calls[0][0]).toBe( + 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', + ); + expect(openSpy.mock.calls[0][1]).toBe('test-popup'); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(0); + + const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; + + await expect(Promise.race([payloadPromise, 'waiting'])).resolves.toBe( + 'waiting', + ); + + listener({} as MessageEvent); + + await expect(Promise.race([payloadPromise, 'waiting'])).resolves.toBe( + 'waiting', + ); + + // None of these should be accepted + listener({ source: popupMock } as MessageEvent); + listener({ origin: 'my-origin' } as MessageEvent); + listener({ data: { type: 'oauth-result' } } as MessageEvent); + listener({ + source: popupMock, + origin: 'my-origin', + data: {}, + } as MessageEvent); + listener({ + source: popupMock, + origin: 'my-origin', + data: { type: 'not-oauth-result', payload: {} }, + } as MessageEvent); + + await expect(Promise.race([payloadPromise, 'waiting'])).resolves.toBe( + 'waiting', + ); + + const myPayload = {}; + + // This should be accepted as a valid sessions response + listener({ + source: popupMock, + origin: 'my-origin', + data: { + type: 'oauth-result', + payload: myPayload, + }, + } as MessageEvent); + + await expect(payloadPromise).resolves.toBe(myPayload); + + expect(openSpy).toBeCalledTimes(1); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(1); + }); + + it('should fail if popup returns error', async () => { + const oauth = new OAuthRequestManager(); + + const popupMock = { closed: false }; + const openSpy = jest + .spyOn(window, 'open') + .mockReturnValue(popupMock as Window); + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + + const payloadPromise = oauth.showLoginPopup({ + url: 'url', + name: 'name', + origin: 'my-origin', + }); + + expect(openSpy).toBeCalledTimes(1); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(0); + + const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; + + listener({ + source: popupMock, + origin: 'my-origin', + data: { + type: 'oauth-result', + payload: { + error: { + message: 'NOPE', + name: 'NopeError', + }, + }, + }, + } as MessageEvent); + + await expect(payloadPromise).rejects.toThrow({ + name: 'NopeError', + message: 'NOPE', + }); + + expect(openSpy).toBeCalledTimes(1); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(1); + }); + + it('should fail if popup is closed', async () => { + const oauth = new OAuthRequestManager(); + + const openSpy = jest + .spyOn(window, 'open') + .mockReturnValue({ closed: false } as Window); + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + const popupMock = { closed: false }; + + openSpy.mockReturnValue(popupMock as Window); + + const payloadPromise = oauth.showLoginPopup({ + url: 'url', + name: 'name', + origin: 'origin', + }); + + expect(openSpy).toBeCalledTimes(1); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(0); + + setTimeout(() => { + popupMock.closed = true; + }, 150); + await expect(payloadPromise).rejects.toThrow( + 'Login failed, popup was closed', + ); + + expect(openSpy).toBeCalledTimes(1); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(1); + }); +}); diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts new file mode 100644 index 0000000000..239d4aebc6 --- /dev/null +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts @@ -0,0 +1,158 @@ +/* + * 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 { + OAuthRequestApi, + LoginPopupOptions, + AuthRequest, + AuthRequester, + AuthRequesterOptions, +} from '../../definitions/oauthrequest'; +import Observable from 'zen-observable'; +import { OAuthPendingRequests, PendingRequest } from './OAuthPendingRequests'; + +export class OAuthRequestManager implements OAuthRequestApi { + private readonly requests$: Observable; + private readonly observers = new Set< + ZenObservable.SubscriptionObserver + >(); + private currentRequests: AuthRequest[] = []; + private handlerCount = 0; + + constructor() { + this.requests$ = new Observable((observer) => { + observer.next(this.currentRequests); + + this.observers.add(observer); + return () => { + this.observers.delete(observer); + }; + }).map((requests) => requests.filter(Boolean)); // Convert from sparse array to array of present items only + } + + createAuthRequester(options: AuthRequesterOptions): AuthRequester { + const handler = new OAuthPendingRequests(); + + const index = this.handlerCount; + this.handlerCount++; + + handler.pending().subscribe({ + next: (scopeRequest) => { + const newRequests = this.currentRequests.slice(); + const request = this.makeAuthRequest(scopeRequest, options); + if (!request) { + delete newRequests[index]; + } else { + newRequests[index] = request; + } + this.currentRequests = newRequests; + this.observers.forEach((observer) => observer.next(newRequests)); + }, + }); + + return (scopes) => { + return handler.request(scopes); + }; + } + + // Converts the pending request and popup options into a popup request that we can forward to subscribers. + private makeAuthRequest( + request: PendingRequest, + options: AuthRequesterOptions, + ): AuthRequest | undefined { + const { scopes } = request; + if (!scopes) { + return undefined; + } + + return { + info: options.info, + triggerAuth: async () => { + const result = await options.authHandler(scopes); + request.resolve(result); + }, + reject: () => { + const error = new Error('Login failed, rejected by user'); + error.name = 'RejectedError'; + request.reject(error); + }, + }; + } + + handleAuthRequests(): Observable { + return this.requests$; + } + + async showLoginPopup(options: LoginPopupOptions): Promise { + return new Promise((resolve, reject) => { + const width = options.width || 500; + const height = options.height || 700; + const left = window.screen.width / 2 - width / 2; + const top = window.screen.height / 2 - height / 2; + + const popup = window.open( + options.url, + options.name, + `menubar=no,location=no,resizable=no,scrollbars=no,status=no,width=${width},height=${height},top=${top},left=${left}`, + ); + + if (!popup || typeof popup.closed === 'undefined' || popup.closed) { + reject(new Error('Failed to open auth popup.')); + return; + } + + const messageListener = (event: MessageEvent) => { + if (event.source !== popup) { + return; + } + if (event.origin !== options.origin) { + return; + } + const { data } = event; + if (data.type !== 'oauth-result') { + return; + } + + if (data.payload.error) { + const error = new Error(data.payload.error.message); + error.name = data.payload.error.name; + // TODO: proper error type + // error.extra = data.payload.error.extra; + reject(error); + } else { + resolve(data.payload); + } + done(); + }; + + const intervalId = setInterval(() => { + if (popup.closed) { + const error = new Error('Login failed, popup was closed'); + error.name = 'PopupClosedError'; + reject(error); + done(); + } + }, 100); + + function done() { + window.removeEventListener('message', messageListener); + clearInterval(intervalId); + } + + window.addEventListener('message', messageListener); + }); + } +} diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/index.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/index.ts new file mode 100644 index 0000000000..0fe9bfae0f --- /dev/null +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/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 { OAuthRequestManager } from './OAuthRequestManager'; diff --git a/packages/core/src/api/apis/implementations/index.ts b/packages/core/src/api/apis/implementations/index.ts index b5ea6467cb..ebd89b54c6 100644 --- a/packages/core/src/api/apis/implementations/index.ts +++ b/packages/core/src/api/apis/implementations/index.ts @@ -20,3 +20,4 @@ export * from './AlertApiForwarder'; export * from './ErrorApiForwarder'; +export * from './OAuthRequestManager'; From 5553d2dc94ed8b22a26f13fef87747b695a3b0f9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 00:45:34 +0200 Subject: [PATCH 55/73] bump react-use to 14.2.0 + add to core --- packages/app/package.json | 2 +- .../default-app/packages/app/package.json.hbs | 2 +- .../plugins/welcome/package.json.hbs | 2 +- .../templates/default-plugin/package.json.hbs | 2 +- packages/core/package.json | 1 + plugins/catalog/package.json | 2 +- plugins/explore/package.json | 2 +- plugins/graphiql/package.json | 2 +- plugins/home-page/package.json | 2 +- plugins/lighthouse/package.json | 2 +- plugins/register-component/package.json | 2 +- plugins/scaffolder/package.json | 2 +- plugins/tech-radar/package.json | 2 +- plugins/welcome/package.json | 2 +- yarn.lock | 28 +++++++++---------- 15 files changed, 28 insertions(+), 27 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index bce1f8c01c..e3b71820b6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -21,7 +21,7 @@ "react": "^16.12.0", "react-dom": "^16.12.0", "react-router-dom": "^5.2.0", - "react-use": "^13.24.0", + "react-use": "^14.2.0", "zen-observable": "^0.8.15" }, "devDependencies": { diff --git a/packages/cli/templates/default-app/packages/app/package.json.hbs b/packages/cli/templates/default-app/packages/app/package.json.hbs index 5f4459f556..24ab67f830 100644 --- a/packages/cli/templates/default-app/packages/app/package.json.hbs +++ b/packages/cli/templates/default-app/packages/app/package.json.hbs @@ -13,7 +13,7 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "^5.2.0", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@testing-library/jest-dom": "^4.2.4", diff --git a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs index 194e81da25..75321fa60c 100644 --- a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs +++ b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs @@ -19,7 +19,7 @@ "@material-ui/icons": "^4.9.1", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^13.24.0", + "react-use": "^14.2.0", "react-router-dom": "^5.2.0" }, "devDependencies": { diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index c4a72c6b06..bbb6592d62 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -21,7 +21,7 @@ "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^{{version}}", diff --git a/packages/core/package.json b/packages/core/package.json index eaf25e07fd..f2a1adc57b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -42,6 +42,7 @@ "react-router-dom": "^5.2.0", "react-sparklines": "^1.7.0", "react-syntax-highlighter": "^12.2.1", + "react-use": "^14.2.0", "zen-observable": "^0.8.15" }, "devDependencies": { diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 3c46b5034f..b34c85e497 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -21,7 +21,7 @@ "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.5", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index c330068940..0750b2bd09 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -22,7 +22,7 @@ "classnames": "^2.2.6", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.5", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index a81ce53e5a..f031ea643c 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -36,7 +36,7 @@ "graphql": "15.0.0", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.5", diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json index 9a30bb394f..015ad1deab 100644 --- a/plugins/home-page/package.json +++ b/plugins/home-page/package.json @@ -21,7 +21,7 @@ "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.5", diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 200897b7c3..62487f3f1d 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -23,7 +23,7 @@ "react-dom": "^16.13.1", "react-markdown": "^4.3.1", "react-router-dom": "^5.2.0", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.5", diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 4fc5af10da..68822e983b 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -22,7 +22,7 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "react-hook-form": "^5.7.2", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.5", diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index ba686fc016..3dc7893ba9 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -21,7 +21,7 @@ "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.5", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index cbddd0c7db..5e6b90b221 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -25,7 +25,7 @@ "prop-types": "^15.7.2", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.5", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index ea1c579af3..b9f9156b45 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -22,7 +22,7 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "^5.2.0", - "react-use": "^13.24.0" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.5", diff --git a/yarn.lock b/yarn.lock index a2c9f54c82..d09e224d25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4201,10 +4201,10 @@ dependencies: "@types/sizzle" "*" -"@types/js-cookie@2.2.5": - version "2.2.5" - resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.5.tgz#38dfaacae8623b37cc0b0d27398e574e3fc28b1e" - integrity sha512-cpmwBRcHJmmZx0OGU7aPVwGWGbs4iKwVYchk9iuMtxNCA2zorwdaTz4GkLgs2WGxiRZRFKnV1k6tRUHX7tBMxg== +"@types/js-cookie@2.2.6": + version "2.2.6" + resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f" + integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw== "@types/json-schema@*", "@types/json-schema@^7.0.3": version "7.0.4" @@ -4971,10 +4971,10 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" -"@xobotyi/scrollbar-width@1.9.4": - version "1.9.4" - resolved "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.4.tgz#a7dce20b7465bcad29cd6bbb557695e4ea7863cb" - integrity sha512-o12FCQt/X5n3pgKEWGpt0f/7Eg4mfv3uRwPUrctiOT8ZuxbH3cNLGWfH/8y6KxVJg4L2885ucuXQ6XECZzUiJA== +"@xobotyi/scrollbar-width@1.9.5": + version "1.9.5" + resolved "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" + integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -18180,13 +18180,13 @@ react-transition-group@^4.0.0, react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" -react-use@^13.24.0: - version "13.27.0" - resolved "https://registry.npmjs.org/react-use/-/react-use-13.27.0.tgz#53a619dc9213e2cbe65d6262e8b0e76641ade4aa" - integrity sha512-2lyTyqJWyvnaP/woVtDcFS4B5pUYz0FQWI9pVHk/6TBWom2x3/ziJthkEn/LbCA9Twv39xSQU7Dn0zdIWfsNTQ== +react-use@^14.2.0: + version "14.2.0" + resolved "https://registry.npmjs.org/react-use/-/react-use-14.2.0.tgz#abac033fae5e358599b7e38084ff11b02e5d4868" + integrity sha512-vwC7jsBsiDENLrXGPqIH3W4mMS2j24h5jp4ol3jiiUQzZhCaG+ihumrShJxBI59hXso1pLHAePRQAg/fJjDcaQ== dependencies: - "@types/js-cookie" "2.2.5" - "@xobotyi/scrollbar-width" "1.9.4" + "@types/js-cookie" "2.2.6" + "@xobotyi/scrollbar-width" "1.9.5" copy-to-clipboard "^3.2.0" fast-deep-equal "^3.1.1" fast-shallow-equal "^1.0.0" From c689244a177c32a43feb56a4f3b19f1698b788d2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 00:48:34 +0200 Subject: [PATCH 56/73] packages/core: added OAuthRequestDialog --- .../LoginRequestListItem.tsx | 81 +++++++++++++++++ .../OAuthRequestDialog/OAuthRequestDialog.tsx | 88 +++++++++++++++++++ .../components/OAuthRequestDialog/index.ts | 17 ++++ packages/core/src/index.ts | 1 + 4 files changed, 187 insertions(+) create mode 100644 packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx create mode 100644 packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx create mode 100644 packages/core/src/components/OAuthRequestDialog/index.ts diff --git a/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx new file mode 100644 index 0000000000..56d69aa814 --- /dev/null +++ b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx @@ -0,0 +1,81 @@ +/* + * 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 { + ListItem, + ListItemAvatar, + ListItemText, + makeStyles, + Typography, + Theme, +} from '@material-ui/core'; +import React, { FC, useState } from 'react'; +import { AuthRequest } from '../../api'; + +const useItemStyles = makeStyles((theme) => ({ + root: { + paddingLeft: theme.spacing(3), + }, +})); + +type RowProps = { + request: AuthRequest; + busy: boolean; + setBusy: (busy: boolean) => void; +}; + +const LoginRequestListItem: FC = ({ request, busy, setBusy }) => { + const classes = useItemStyles(); + const [error, setError] = useState(); + + const handleContinue = async () => { + setBusy(true); + try { + await request.triggerAuth(); + } catch (e) { + setError(e); + } finally { + setBusy(false); + } + }; + + const IconComponent = request.info.icon; + + return ( + + + + + + {error.message || 'An unspecified error occurred'} + + ) + } + /> + + ); +}; + +export default LoginRequestListItem; diff --git a/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx b/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx new file mode 100644 index 0000000000..2a105fe5e3 --- /dev/null +++ b/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx @@ -0,0 +1,88 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + List, + makeStyles, + Theme, + Button, +} from '@material-ui/core'; +import React, { FC, useMemo, useState } from 'react'; +import { useObservable } from 'react-use'; +import LoginRequestListItem from './LoginRequestListItem'; +import { useApi, oauthRequestApiRef } from '../../api'; + +const useStyles = makeStyles((theme) => ({ + dialog: { + paddingTop: theme.spacing(1), + }, + title: { + minWidth: 0, + }, + contentList: { + padding: 0, + }, +})); + +type OAuthRequestDialogProps = {}; + +export const OAuthRequestDialog: FC = () => { + const classes = useStyles(); + const [busy, setBusy] = useState(false); + const oauthRequestApi = useApi(oauthRequestApiRef); + const requests = useObservable( + useMemo(() => oauthRequestApi.handleAuthRequests(), [oauthRequestApi]), + [], + ); + + const handleRejectAll = () => { + requests.forEach((request) => request.reject()); + }; + + return ( + + + Login Required + + + + + {requests.map((request) => ( + + ))} + + + + + + + + ); +}; diff --git a/packages/core/src/components/OAuthRequestDialog/index.ts b/packages/core/src/components/OAuthRequestDialog/index.ts new file mode 100644 index 0000000000..45f87ece1d --- /dev/null +++ b/packages/core/src/components/OAuthRequestDialog/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 { OAuthRequestDialog } from './OAuthRequestDialog'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e817ada9c9..1bd35f67c3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -33,6 +33,7 @@ export { default as HorizontalProgress } from './components/ProgressBars/Horizon export { default as CopyTextButton } from './components/CopyTextButton'; export { default as Progress } from './components/Progress'; export * from './components/SimpleStepper'; +export { OAuthRequestDialog } from './components/OAuthRequestDialog'; export { AlphaLabel, BetaLabel } from './components/Lifecycle'; export { default as SupportButton } from './components/SupportButton'; export { default as Table, SubvalueCell } from './components/Table'; From 1c50ded618dddadc85b299652cc42ef7310b253f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 01:17:32 +0200 Subject: [PATCH 57/73] packages/core: added some basic docs for OAuthRequestManager --- .../OAuthRequestManager/BasicOAuthScopes.ts | 4 ++++ .../OAuthRequestManager/OAuthPendingRequests.ts | 5 +++++ .../OAuthRequestManager/OAuthRequestManager.ts | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts index 053d017da8..14aecd1607 100644 --- a/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts @@ -16,6 +16,10 @@ import { OAuthScopes, OAuthScopeLike } from '../../definitions/oauthrequest'; +/** + * The BasicOAuthScopes class is an implementation of OAuthScopes that + * works for any simple comma- or space-separated format of scope. + */ export class BasicOAuthScopes implements OAuthScopes { static from(scopes: OAuthScopeLike, normalizer?: (scope: string) => string) { const normalized = BasicOAuthScopes.asStrings(scopes, normalizer); diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts index 2398282842..bc1b33ca5e 100644 --- a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts @@ -36,6 +36,11 @@ export type OAuthPendingRequestsApi = { pending(): Observable>; }; +/** + * The OAuthPendingRequests class is a utility for managing and observing + * a stream of requests for oauth scopes, and resolving them correctly once + * requests are fulfilled. + */ export class OAuthPendingRequests implements OAuthPendingRequestsApi { private requests: RequestQueueEntry[] = []; diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts index 239d4aebc6..c8cfacd161 100644 --- a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts @@ -24,6 +24,13 @@ import { import Observable from 'zen-observable'; import { OAuthPendingRequests, PendingRequest } from './OAuthPendingRequests'; +/** + * The OAuthRequestManager is an implementation of the OAuthRequestApi. + * + * The purpose of this class and the API is to read a stream of incoming requests + * of OAuth access tokens from different providers with varying scope, and funnel + * them all together into a single requests for each OAuth provider. + */ export class OAuthRequestManager implements OAuthRequestApi { private readonly requests$: Observable; private readonly observers = new Set< From eec599738d436fa467251a388841be4c9a0d9041 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 11:02:35 +0200 Subject: [PATCH 58/73] packages/core: naming and docs updates for OAuthRequest API --- .../src/api/apis/definitions/oauthrequest.ts | 50 ++++++++++++------- .../OAuthPendingRequests.ts | 4 +- .../OAuthRequestManager.ts | 24 ++++----- packages/core/src/api/types.ts | 2 +- .../LoginRequestListItem.tsx | 10 ++-- .../OAuthRequestDialog/OAuthRequestDialog.tsx | 4 +- 6 files changed, 53 insertions(+), 41 deletions(-) diff --git a/packages/core/src/api/apis/definitions/oauthrequest.ts b/packages/core/src/api/apis/definitions/oauthrequest.ts index b31b828183..09e5f77718 100644 --- a/packages/core/src/api/apis/definitions/oauthrequest.ts +++ b/packages/core/src/api/apis/definitions/oauthrequest.ts @@ -63,10 +63,10 @@ export type LoginPopupOptions = { /** * Information about the auth provider that we're requesting a login towards. * - * This should be show to the user so that they can be informed about what login is being requested + * This should be shown to the user so that they can be informed about what login is being requested * before a popup is shown. */ -export type AuthProviderInfo = { +export type AuthProvider = { /** * Title for the auth provider, for example "GitHub" */ @@ -82,39 +82,45 @@ export type AuthProviderInfo = { * Describes how to handle auth requests. Both how to show them to the user, and what to do when * the user accesses the auth request. */ -export type AuthRequesterOptions = { +export type AuthRequesterOptions = { /** * Information about the auth provider, which will be forwarded to auth requests. */ - info: AuthProviderInfo; + provider: AuthProvider; /** * Implementation of the auth flow, which will be called synchronously when - * triggerAuth() is called on an auth requests. + * trigger() is called on an auth requests. */ - authHandler(scope: OAuthScopes): Promise; + onAuthRequest(scope: OAuthScopes): Promise; }; /** * Function used to trigger new auth requests for a set of scopes. * - * The returned promise will resolve to the same value returned by the authHandler in the + * The returned promise will resolve to the same value returned by the onAuthRequest in the * AuthRequesterOptions. Or rejected, if the request is rejected. * * This function can be called multiple times before the promise resolves. All calls - * will be merged into one request, and the scopes forwarded to the authHandler will be the + * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the * union of all requested scopes. */ -export type AuthRequester = (scope: OAuthScopes) => Promise; +export type AuthRequester = ( + scope: OAuthScopes, +) => Promise; /** - * An auth request for a single auth provider. + * An pending auth request for a single auth provider. The request will remain in this pending + * state until either reject() or trigger() is called. + * + * Any new requests for the same provider are merged into the existing pending request, meaning + * there will only ever be a single pending request for a given provider. */ -export type AuthRequest = { +export type PendingAuthRequest = { /** * Information about the auth provider, as given in the AuthRequesterOptions */ - info: AuthProviderInfo; + provider: AuthProvider; /** * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". @@ -122,9 +128,11 @@ export type AuthRequest = { reject: () => void; /** - * Synchronously calls the auth handler with all scope currently in the request. + * Trigger the auth request to continue the auth flow, by for example showing a popup. + * + * Synchronously calls onAuthRequest with all scope currently in the request. */ - triggerAuth(): Promise; + trigger(): Promise; }; /** @@ -136,6 +144,8 @@ export type OAuthRequestApi = { * * The redirect handler of the flow should use postMessage to communicate back * to the app window. + * + * The returned promise resolves to the contents of the message that was posted from the auth popup. */ showLoginPopup(options: LoginPopupOptions): Promise; @@ -143,7 +153,7 @@ export type OAuthRequestApi = { * A utility for showing login popups or similar things, and merging together multiple requests for * different scopes into one request that inclues all scopes. * - * The passed in options provide information about the login provider, and how handle auth requests. + * The passed in options provide information about the login provider, and how to handle auth requests. * * The returned AuthRequester function is used to request login with new scopes. These requests * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests @@ -151,10 +161,12 @@ export type OAuthRequestApi = { * * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. */ - createAuthRequester(options: AuthRequesterOptions): AuthRequester; + createAuthRequester( + options: AuthRequesterOptions, + ): AuthRequester; /** - * Subscribes as a handler of auth requests. The returned observable will emit all + * Observers panding auth requests. The returned observable will emit all * current active auth request, at most one for each created auth requester. * * Each request has its own info about the login provider, forwarded from the auth requester options. @@ -162,9 +174,9 @@ export type OAuthRequestApi = { * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". * If a auth is triggered, and the auth handler resolves successfully, then all currently pending - * AuthRequester calls will resolve to the value returned by the authHandler call. + * AuthRequester calls will resolve to the value returned by the onAuthRequest call. */ - handleAuthRequests(): Observable; + authRequest$(): Observable; }; export const oauthRequestApiRef = new ApiRef({ diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts index bc1b33ca5e..904acecf9f 100644 --- a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts @@ -38,8 +38,8 @@ export type OAuthPendingRequestsApi = { /** * The OAuthPendingRequests class is a utility for managing and observing - * a stream of requests for oauth scopes, and resolving them correctly once - * requests are fulfilled. + * a stream of requests for oauth scopes for a single provider, and resolving + * them correctly once requests are fulfilled. */ export class OAuthPendingRequests implements OAuthPendingRequestsApi { diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts index c8cfacd161..dd31a4b33c 100644 --- a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts @@ -17,7 +17,7 @@ import { OAuthRequestApi, LoginPopupOptions, - AuthRequest, + PendingAuthRequest, AuthRequester, AuthRequesterOptions, } from '../../definitions/oauthrequest'; @@ -29,18 +29,18 @@ import { OAuthPendingRequests, PendingRequest } from './OAuthPendingRequests'; * * The purpose of this class and the API is to read a stream of incoming requests * of OAuth access tokens from different providers with varying scope, and funnel - * them all together into a single requests for each OAuth provider. + * them all together into a single request for each OAuth provider. */ export class OAuthRequestManager implements OAuthRequestApi { - private readonly requests$: Observable; + private readonly request$: Observable; private readonly observers = new Set< - ZenObservable.SubscriptionObserver + ZenObservable.SubscriptionObserver >(); - private currentRequests: AuthRequest[] = []; + private currentRequests: PendingAuthRequest[] = []; private handlerCount = 0; constructor() { - this.requests$ = new Observable((observer) => { + this.request$ = new Observable((observer) => { observer.next(this.currentRequests); this.observers.add(observer); @@ -79,16 +79,16 @@ export class OAuthRequestManager implements OAuthRequestApi { private makeAuthRequest( request: PendingRequest, options: AuthRequesterOptions, - ): AuthRequest | undefined { + ): PendingAuthRequest | undefined { const { scopes } = request; if (!scopes) { return undefined; } return { - info: options.info, - triggerAuth: async () => { - const result = await options.authHandler(scopes); + provider: options.provider, + trigger: async () => { + const result = await options.onAuthRequest(scopes); request.resolve(result); }, reject: () => { @@ -99,8 +99,8 @@ export class OAuthRequestManager implements OAuthRequestApi { }; } - handleAuthRequests(): Observable { - return this.requests$; + authRequest$(): Observable { + return this.request$; } async showLoginPopup(options: LoginPopupOptions): Promise { diff --git a/packages/core/src/api/types.ts b/packages/core/src/api/types.ts index 28a6155017..09f0bc11ae 100644 --- a/packages/core/src/api/types.ts +++ b/packages/core/src/api/types.ts @@ -32,7 +32,7 @@ export type Observer = { */ export type Subscription = { /** - * CAncels the subscription + * Cancels the subscription */ unsubscribe(): void; diff --git a/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx index 56d69aa814..8e3d98c998 100644 --- a/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx +++ b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx @@ -23,7 +23,7 @@ import { Theme, } from '@material-ui/core'; import React, { FC, useState } from 'react'; -import { AuthRequest } from '../../api'; +import { PendingAuthRequest } from '../../api'; const useItemStyles = makeStyles((theme) => ({ root: { @@ -32,7 +32,7 @@ const useItemStyles = makeStyles((theme) => ({ })); type RowProps = { - request: AuthRequest; + request: PendingAuthRequest; busy: boolean; setBusy: (busy: boolean) => void; }; @@ -44,7 +44,7 @@ const LoginRequestListItem: FC = ({ request, busy, setBusy }) => { const handleContinue = async () => { setBusy(true); try { - await request.triggerAuth(); + await request.trigger(); } catch (e) { setError(e); } finally { @@ -52,7 +52,7 @@ const LoginRequestListItem: FC = ({ request, busy, setBusy }) => { } }; - const IconComponent = request.info.icon; + const IconComponent = request.provider.icon; return ( = ({ request, busy, setBusy }) => { diff --git a/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx b/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx index 2a105fe5e3..617cce2aee 100644 --- a/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx +++ b/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx @@ -48,7 +48,7 @@ export const OAuthRequestDialog: FC = () => { const [busy, setBusy] = useState(false); const oauthRequestApi = useApi(oauthRequestApiRef); const requests = useObservable( - useMemo(() => oauthRequestApi.handleAuthRequests(), [oauthRequestApi]), + useMemo(() => oauthRequestApi.authRequest$(), [oauthRequestApi]), [], ); @@ -71,7 +71,7 @@ export const OAuthRequestDialog: FC = () => { {requests.map((request) => ( Date: Thu, 14 May 2020 11:17:13 +0200 Subject: [PATCH 59/73] packages/cli: only ship js files in dist --- packages/cli/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 8e485dc5ac..2a3ad81673 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -16,7 +16,7 @@ "backstage" ], "license": "Apache-2.0", - "main": "dist", + "main": "dist/index.js", "scripts": { "build": "backstage-cli build-cache -- tsc --project tsconfig.build.json", "lint": "backstage-cli lint", @@ -89,7 +89,7 @@ "templates", "config", "bin", - "dist" + "dist/**/*.js" ], "nodemonConfig": { "watch": "./src", From d76f8714a44f4e93859205663c74c08f46cbd1d2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 11:31:35 +0200 Subject: [PATCH 60/73] packages,plugins: only publish .js and .d.ts files in dist --- .../cli/templates/default-app/plugins/welcome/package.json.hbs | 2 +- packages/cli/templates/default-plugin/package.json.hbs | 2 +- packages/core/package.json | 2 +- packages/dev-utils/package.json | 2 +- packages/test-utils-core/package.json | 2 +- packages/test-utils/package.json | 2 +- packages/theme/package.json | 2 +- plugins/catalog/package.json | 2 +- plugins/explore/package.json | 2 +- plugins/graphiql/package.json | 2 +- plugins/home-page/package.json | 2 +- plugins/lighthouse/package.json | 2 +- plugins/register-component/package.json | 2 +- plugins/scaffolder/package.json | 2 +- plugins/tech-radar/package.json | 2 +- plugins/welcome/package.json | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs index 194e81da25..8ec49d0d69 100644 --- a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs +++ b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs @@ -31,6 +31,6 @@ "jest-fetch-mock": "^3.0.3" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index c4a72c6b06..b1f0506967 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -35,6 +35,6 @@ "jest-fetch-mock": "^3.0.3" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/packages/core/package.json b/packages/core/package.json index df69a9353b..49e5a8fe0d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,6 +58,6 @@ "@types/react-sparklines": "^1.7.0" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 605bd8b6ef..a0ceb3497e 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -42,6 +42,6 @@ "react-router-dom": "^5.2.0" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index ce13becdb2..dcf8b414b1 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -33,6 +33,6 @@ "react-dom": "^16.12.0" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 512af5020f..a22be3625d 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -40,6 +40,6 @@ "react-router-dom": "^5.2.0" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/packages/theme/package.json b/packages/theme/package.json index 38392959e8..af1543d09d 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -30,6 +30,6 @@ "@backstage/cli": "^0.1.1-alpha.5" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 3c46b5034f..9567f19720 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -35,6 +35,6 @@ "jest-fetch-mock": "^3.0.3" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/explore/package.json b/plugins/explore/package.json index c330068940..74686772ba 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -37,6 +37,6 @@ "jest-fetch-mock": "^3.0.3" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index a81ce53e5a..96d6e140a9 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -52,6 +52,6 @@ "react-router-dom": "^5.2.0" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json index 9a30bb394f..80ac6c8d29 100644 --- a/plugins/home-page/package.json +++ b/plugins/home-page/package.json @@ -36,6 +36,6 @@ "react-router-dom": "^5.2.0" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 200897b7c3..4da92d3edd 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -38,6 +38,6 @@ "jest-fetch-mock": "^3.0.3" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 4fc5af10da..948e5172ff 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -36,6 +36,6 @@ "jest-fetch-mock": "^3.0.3" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index ba686fc016..eb865bc9bb 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -35,6 +35,6 @@ "jest-fetch-mock": "^3.0.3" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index cbddd0c7db..40637a93d8 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -41,6 +41,6 @@ "jest-fetch-mock": "^3.0.3" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index ea1c579af3..5c623f754e 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -36,6 +36,6 @@ "jest-fetch-mock": "^3.0.3" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } From bdec811fe1736d119040aa48664dbf09c120ca87 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 11:34:21 +0200 Subject: [PATCH 61/73] packages/cli: update templates to use latest @types/jest version --- .../cli/templates/default-app/packages/app/package.json.hbs | 2 +- packages/cli/templates/default-plugin/package.json.hbs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/templates/default-app/packages/app/package.json.hbs b/packages/cli/templates/default-app/packages/app/package.json.hbs index 5f4459f556..f8553f915e 100644 --- a/packages/cli/templates/default-app/packages/app/package.json.hbs +++ b/packages/cli/templates/default-app/packages/app/package.json.hbs @@ -19,7 +19,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/react-router-dom": "^5.1.3", "@types/testing-library__jest-dom": "^5.0.4", diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index c4a72c6b06..a1a1929495 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -29,7 +29,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3" From b4afc0bc9b36bf675d337a5bb010fb979a51b5c7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 11:36:15 +0200 Subject: [PATCH 62/73] package.json: add root lerna diff command --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 4f7eb2c5f5..f98ac41ad2 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "bundle": "yarn build && yarn workspace example-app bundle", "build": "lerna run build", "clean": "lerna run clean", + "diff": "lerna run diff --", "test": "yarn build && lerna run test --since origin/master -- --coverage", "test:all": "yarn build && lerna run test -- --coverage", "lint": "lerna run lint --since origin/master --", From 4fb230e48f1d851c587857b3bf85ea9bb40b06ff Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 11:37:33 +0200 Subject: [PATCH 63/73] github/workflows: add check to make sure plugin templates stay in sync --- .github/workflows/frontend.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index f62f439119..c19583b653 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -56,6 +56,9 @@ jobs: - name: yarn install run: yarn install --frozen-lockfile + - name: verify plugin template + run: yarn lerna -- run diff -- --check + - name: lint run: yarn lerna -- run lint --since origin/master From 75395ecff189e69c02229f4e117ab5a404ee282e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 11:51:45 +0200 Subject: [PATCH 64/73] packages/cli: add publish config sync to plugin:diff --- .../cli/src/commands/plugin/diff/handlers.ts | 28 +++++++++++++++++++ .../templates/default-plugin/package.json.hbs | 3 ++ 2 files changed, 31 insertions(+) diff --git a/packages/cli/src/commands/plugin/diff/handlers.ts b/packages/cli/src/commands/plugin/diff/handlers.ts index 32079415ea..87518eab3e 100644 --- a/packages/cli/src/commands/plugin/diff/handlers.ts +++ b/packages/cli/src/commands/plugin/diff/handlers.ts @@ -54,6 +54,7 @@ class PackageJsonHandler { await this.syncField('types'); await this.syncField('files'); await this.syncScripts(); + await this.syncPublishConfig(); await this.syncDependencies('dependencies'); await this.syncDependencies('devDependencies'); } @@ -105,6 +106,33 @@ class PackageJsonHandler { } } + private async syncPublishConfig() { + const pkgPublishConf = this.pkg.publishConfig; + const targetPublishConf = this.targetPkg.publishConfig; + + // If template doesn't have a publish config we're done + if (!pkgPublishConf) { + return; + } + + // Publish config can be removed the the target, skip in that case + if (!targetPublishConf) { + return; + } + + for (const key of Object.keys(pkgPublishConf)) { + // Don't want to mess with peoples internal setup + if (!['access', 'registry'].includes(key)) { + await this.syncField( + key, + pkgPublishConf, + targetPublishConf, + 'publishConfig', + ); + } + } + } + private async syncDependencies(fieldName: string) { const pkgDeps = this.pkg[fieldName]; const targetDeps = (this.targetPkg[fieldName] = diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index c4a72c6b06..2cdd8fe03d 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -5,6 +5,9 @@ "types": "dist/index.d.ts", "license": "Apache-2.0", "private": true, + "publishConfig": { + "access": "public" + }, "scripts": { "build": "backstage-cli plugin:build", "start": "backstage-cli plugin:serve", From 9a2d473346cd00ac9bd11268041af35b06f2fe51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 14 May 2020 13:16:04 +0200 Subject: [PATCH 65/73] Link components to the location that created them --- packages/backend/src/index.ts | 18 +++++++------ .../src/catalog/CatalogLogic.test.ts | 1 + .../src/catalog/CatalogLogic.ts | 6 ++--- .../src/catalog/DatabaseCatalog.test.ts | 3 +++ .../src/catalog/DatabaseCatalog.ts | 27 ++++++++++++++----- .../src/catalog/StaticCatalog.ts | 11 +++++--- plugins/catalog-backend/src/catalog/types.ts | 6 ++++- .../src/descriptors/component.ts | 13 +++++---- .../catalog-backend/src/descriptors/util.ts | 7 ++--- .../src/ingestion/fileLocation.ts | 7 ++--- .../catalog-backend/src/ingestion/types.ts | 7 +++-- plugins/catalog-backend/src/ingestion/util.ts | 7 +++-- .../src/migrations/20200511113813_init.ts | 17 +++++++++++- 13 files changed, 89 insertions(+), 41 deletions(-) diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 8c685d3fb7..8a7a4c9b72 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -41,14 +41,16 @@ const DEFAULT_PORT = 7000; const PORT = parseInt(process.env.PORT ?? '', 10) || DEFAULT_PORT; function createEnv(plugin: string): PluginEnvironment { - return { - logger: getRootLogger().child({ type: 'plugin', plugin }), - database: knex({ - client: 'sqlite3', - connection: ':memory:', - useNullAsDefault: true, - }), - }; + const logger = getRootLogger().child({ type: 'plugin', plugin }); + const database = knex({ + client: 'sqlite3', + connection: ':memory:', + useNullAsDefault: true, + }); + database.client.pool.on('createSuccess', (_eventId: any, resource: any) => { + resource.run('PRAGMA foreign_keys = ON', () => {}); + }); + return { logger, database }; } async function main() { diff --git a/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts b/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts index 38f1088b98..5234136b55 100644 --- a/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts +++ b/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts @@ -67,6 +67,7 @@ describe('CatalogLogic', () => { expect(catalog.addOrUpdateComponent).toHaveBeenCalledTimes(1); expect(catalog.addOrUpdateComponent).toHaveBeenNthCalledWith( 1, + '123', expect.objectContaining({ name: 'c1' }), ); }); diff --git a/plugins/catalog-backend/src/catalog/CatalogLogic.ts b/plugins/catalog-backend/src/catalog/CatalogLogic.ts index ed195386d0..686aba0c40 100644 --- a/plugins/catalog-backend/src/catalog/CatalogLogic.ts +++ b/plugins/catalog-backend/src/catalog/CatalogLogic.ts @@ -56,9 +56,9 @@ export class CatalogLogic { for (const location of locations) { try { logger.debug(`Attempting refresh of location: ${location.id}`); - const components = await reader(location); - for (const component of components) { - await catalog.addOrUpdateComponent(component); + const componentDescriptors = await reader(location); + for (const componentDescriptor of componentDescriptors) { + await catalog.addOrUpdateComponent(location.id, componentDescriptor); } } catch (e) { logger.debug(`Failed to update location "${location.id}", ${e}`); diff --git a/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts b/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts index 7a474246d9..98a5e9acd4 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.ts @@ -26,6 +26,9 @@ describe('DatabaseCatalog', () => { connection: ':memory:', useNullAsDefault: true, }); + database.client.pool.on('createSuccess', (_eventId: any, resource: any) => { + resource.run('PRAGMA foreign_keys = ON', () => {}); + }); const logger = winston.createLogger({ transports: [new winston.transports.Stream({ stream: new PassThrough() })], diff --git a/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts index 39a0719201..87ff5a503f 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts @@ -19,6 +19,7 @@ import Knex from 'knex'; import path from 'path'; import { v4 as uuidv4 } from 'uuid'; import { Logger } from 'winston'; +import { ComponentDescriptor } from '../descriptors'; import { readLocation } from '../ingestion'; import { CatalogLogic } from './CatalogLogic'; import { AddLocationRequest, Catalog, Component, Location } from './types'; @@ -44,15 +45,27 @@ export class DatabaseCatalog implements Catalog { private readonly logger: Logger, ) {} - async addOrUpdateComponent(component: Component): Promise { + async addOrUpdateComponent( + locationId: string, + descriptor: ComponentDescriptor, + ): Promise { + const component: Component = { + name: descriptor.metadata.name, + }; + await this.database.transaction(async (tx) => { - await tx('components') - .insert(component) - .catch(() => - tx('components').where({ name: component.name }).update(component), - ); + // TODO(freben): Currently, several locations can compete for the same component + const count = await tx('components') + .where({ name: component.name }) + .update({ ...component, locationId }); + if (!count) { + await tx('components').insert({ + ...component, + id: uuidv4(), + locationId, + }); + } }); - return component; } async components(): Promise { diff --git a/plugins/catalog-backend/src/catalog/StaticCatalog.ts b/plugins/catalog-backend/src/catalog/StaticCatalog.ts index b98798f644..28d6cff9a3 100644 --- a/plugins/catalog-backend/src/catalog/StaticCatalog.ts +++ b/plugins/catalog-backend/src/catalog/StaticCatalog.ts @@ -16,6 +16,7 @@ import { NotFoundError } from '@backstage/backend-common'; import { v4 as uuidv4 } from 'uuid'; +import { ComponentDescriptor } from '../descriptors'; import { AddLocationRequest, Catalog, Component, Location } from './types'; export class StaticCatalog implements Catalog { @@ -27,11 +28,13 @@ export class StaticCatalog implements Catalog { this._locations = locations; } - async addOrUpdateComponent(component: Component): Promise { + async addOrUpdateComponent( + locationId: string, + descriptor: ComponentDescriptor, + ): Promise { this._components = this._components - .filter((c) => c.name !== component.name) - .concat([component]); - return component; + .filter((c) => c.name !== descriptor.metadata.name) + .concat([{ name: descriptor.metadata.name }]); } async components(): Promise { diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index ed33f35b3d..3a7b685003 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -15,6 +15,7 @@ */ import * as yup from 'yup'; +import { ComponentDescriptor } from '../descriptors'; export type Component = { name: string; @@ -39,7 +40,10 @@ export const addLocationRequestShape: yup.Schema = yup .noUnknown(); export type Catalog = { - addOrUpdateComponent(component: Component): Promise; + addOrUpdateComponent( + locationId: string, + descriptor: ComponentDescriptor, + ): Promise; components(): Promise; component(id: string): Promise; addLocation(location: AddLocationRequest): Promise; diff --git a/plugins/catalog-backend/src/descriptors/component.ts b/plugins/catalog-backend/src/descriptors/component.ts index 0ecaf77c66..abe75a0050 100644 --- a/plugins/catalog-backend/src/descriptors/component.ts +++ b/plugins/catalog-backend/src/descriptors/component.ts @@ -15,7 +15,6 @@ */ import * as yup from 'yup'; -import { Component } from '../catalog/types'; import { DescriptorEnvelope } from './envelope'; export type ComponentDescriptor = { @@ -28,6 +27,10 @@ export type ComponentDescriptor = { }; const componentDescriptorSchema: yup.Schema = yup.object({ + kind: yup + .string() + .required() + .matches(/^Component$/), metadata: yup.object({ name: yup.string().required(), }), @@ -38,7 +41,7 @@ const componentDescriptorSchema: yup.Schema = yup.object({ export async function parseComponentDescriptor( envelope: DescriptorEnvelope, -): Promise { +): Promise { let componentDescriptor; try { componentDescriptor = await componentDescriptorSchema.validate(envelope, { @@ -48,9 +51,5 @@ export async function parseComponentDescriptor( throw new Error(`Malformed component, ${e}`); } - const component: Component = { - name: componentDescriptor.metadata.name, - }; - - return [component]; + return [componentDescriptor]; } diff --git a/plugins/catalog-backend/src/descriptors/util.ts b/plugins/catalog-backend/src/descriptors/util.ts index e7b4944215..20118c7d97 100644 --- a/plugins/catalog-backend/src/descriptors/util.ts +++ b/plugins/catalog-backend/src/descriptors/util.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import { Component } from '../catalog/types'; -import { parseComponentDescriptor } from './component'; +import { ComponentDescriptor, parseComponentDescriptor } from './component'; import { parseDescriptorEnvelope } from './envelope'; // TODO(freben): Temporary helper that ignores the kind -export async function parseDescriptor(rawYaml: string): Promise { +export async function parseDescriptor( + rawYaml: string, +): Promise { const env = await parseDescriptorEnvelope(rawYaml); return await parseComponentDescriptor(env); } diff --git a/plugins/catalog-backend/src/ingestion/fileLocation.ts b/plugins/catalog-backend/src/ingestion/fileLocation.ts index 3c1b985356..e856e701d3 100644 --- a/plugins/catalog-backend/src/ingestion/fileLocation.ts +++ b/plugins/catalog-backend/src/ingestion/fileLocation.ts @@ -15,10 +15,11 @@ */ import fs from 'fs-extra'; -import { Component } from '../catalog/types'; -import { parseDescriptor } from '../descriptors'; +import { ComponentDescriptor, parseDescriptor } from '../descriptors'; -export async function readFileLocation(target: string): Promise { +export async function readFileLocation( + target: string, +): Promise { let rawYaml; try { rawYaml = await fs.readFile(target, 'utf8'); diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 5808c7e592..426188ed2c 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -14,6 +14,9 @@ * limitations under the License. */ -import { Component, Location } from '../catalog'; +import { Location } from '../catalog'; +import { ComponentDescriptor } from '../descriptors'; -export type LocationReader = (location: Location) => Promise; +export type LocationReader = ( + location: Location, +) => Promise; diff --git a/plugins/catalog-backend/src/ingestion/util.ts b/plugins/catalog-backend/src/ingestion/util.ts index 91b7ad2678..ede6b1b21a 100644 --- a/plugins/catalog-backend/src/ingestion/util.ts +++ b/plugins/catalog-backend/src/ingestion/util.ts @@ -14,10 +14,13 @@ * limitations under the License. */ -import { Component, Location } from '../catalog/types'; +import { Location } from '../catalog/types'; +import { ComponentDescriptor } from '../descriptors'; import { readFileLocation } from './fileLocation'; -export async function readLocation(location: Location): Promise { +export async function readLocation( + location: Location, +): Promise { switch (location.type) { case 'file': return await readFileLocation(location.target); diff --git a/plugins/catalog-backend/src/migrations/20200511113813_init.ts b/plugins/catalog-backend/src/migrations/20200511113813_init.ts index 0db92a1f45..4c71fefddd 100644 --- a/plugins/catalog-backend/src/migrations/20200511113813_init.ts +++ b/plugins/catalog-backend/src/migrations/20200511113813_init.ts @@ -19,6 +19,9 @@ import * as Knex from 'knex'; export async function up(knex: Knex): Promise { return knex.schema .createTable('locations', (table) => { + table.comment( + 'Registered locations that shall be contiuously scanned for catalog item updates', + ); table.uuid('id').primary().comment('Auto-generated ID of the location'); table.string('type').notNullable().comment('The type of location'); table @@ -27,7 +30,19 @@ export async function up(knex: Knex): Promise { .comment('The actual target of the location'); }) .createTable('components', (table) => { - table.string('name').primary().comment('The name of the component'); + table.comment('All components currently stored in the catalog'); + table.uuid('id').primary().comment('Auto-generated ID of the component'); + table + .uuid('locationId') + .references('id') + .inTable('locations') + .nullable() + .comment('The location that originated the component'); + table + .string('name') + .unique() + .notNullable() + .comment('The external name of the component, as used in references'); }); } From 682d9ce6125cec397cad1ff4c3719993a8c0d1e4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 12:03:02 +0200 Subject: [PATCH 66/73] packages,plugins: point types to src/ for development + add pre/post back cli commands --- packages/backend-common/package.json | 3 ++ packages/cli/src/commands/pack.ts | 34 +++++++++++++++++++ packages/cli/src/index.ts | 17 ++++++++-- .../plugins/welcome/package.json.hbs | 4 ++- .../templates/default-plugin/package.json.hbs | 4 ++- packages/core/package.json | 4 ++- packages/dev-utils/package.json | 4 ++- packages/test-utils-core/package.json | 4 ++- packages/test-utils/package.json | 4 ++- packages/theme/package.json | 4 ++- plugins/catalog/package.json | 4 ++- plugins/explore/package.json | 4 ++- plugins/graphiql/package.json | 4 ++- plugins/home-page/package.json | 4 ++- plugins/lighthouse/package.json | 4 ++- plugins/register-component/package.json | 4 ++- plugins/scaffolder-backend/package.json | 3 ++ plugins/scaffolder/package.json | 4 ++- plugins/tech-radar/package.json | 4 ++- plugins/welcome/package.json | 4 ++- 20 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 packages/cli/src/commands/pack.ts diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 55f66c66a1..27ef241f3d 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -3,6 +3,7 @@ "description": "Common functionality library for Backstage backends", "version": "0.1.1-alpha.5", "main": "dist", + "types": "src/index.ts", "private": false, "publishConfig": { "access": "public" @@ -21,6 +22,8 @@ "build": "backstage-cli build-cache -- tsc", "lint": "backstage-cli lint", "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/packages/cli/src/commands/pack.ts b/packages/cli/src/commands/pack.ts new file mode 100644 index 0000000000..ba9c7725ae --- /dev/null +++ b/packages/cli/src/commands/pack.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs-extra'; +import { paths } from '../lib/paths'; + +export const pre = async () => { + const pkgPath = paths.resolveTarget('package.json'); + + const pkg = await fs.readJson(pkgPath); + pkg.types = 'dist/index.d.ts'; + await fs.writeJson(pkgPath, pkg, { encoding: 'utf8', spaces: 2 }); +}; + +export const post = async () => { + const pkgPath = paths.resolveTarget('package.json'); + + const pkg = await fs.readJson(pkgPath); + pkg.types = 'src/index.ts'; + await fs.writeJson(pkgPath, pkg, { encoding: 'utf8', spaces: 2 }); +}; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index e20cef2615..cf89a3c47d 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -82,6 +82,16 @@ const main = (argv: string[]) => { .description('Run tests, forwarding args to Jest, defaulting to watch mode') .action(actionHandler(() => require('./commands/testCommand'))); + program + .command('prepack') + .description('Prepares a package for packaging before publishing') + .action(actionHandler(() => require('./commands/pack').pre)); + + program + .command('postpack') + .description('Restores the changes made by the prepack command') + .action(actionHandler(() => require('./commands/pack').post)); + program .command('watch-deps') .option('--build', 'Build all dependencies on startup') @@ -129,11 +139,14 @@ const main = (argv: string[]) => { // Wraps an action function so that it always exits and handles errors function actionHandler( - actionRequireFunc: () => { default(...args: T): Promise }, + actionRequireFunc: + | (() => { default(...args: T): Promise }) + | (() => (...args: T) => Promise), ): (...args: T) => Promise { return async (...args: T) => { try { - const actionFunc = actionRequireFunc().default; + const ret = actionRequireFunc(); + const actionFunc = typeof ret === 'function' ? ret : ret.default; await actionFunc(...args); process.exit(0); } catch (error) { diff --git a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs index 75321fa60c..19788fc902 100644 --- a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs +++ b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs @@ -2,7 +2,7 @@ "name": "plugin-welcome", "version": "0.0.0", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "private": true, "scripts": { "build": "backstage-cli plugin:build", @@ -10,6 +10,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index 896266b5a5..6fb7ab9d61 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -2,7 +2,7 @@ "name": "@backstage/plugin-{{id}}", "version": "{{version}}", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -11,6 +11,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/packages/core/package.json b/packages/core/package.json index f2a1adc57b..eeb007b9e6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -17,11 +17,13 @@ ], "license": "Apache-2.0", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "scripts": { "build": "backstage-cli plugin:build", "lint": "backstage-cli lint", "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 605bd8b6ef..b1c22fc100 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -17,11 +17,13 @@ ], "license": "Apache-2.0", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "scripts": { "build": "backstage-cli plugin:build", "lint": "backstage-cli lint", "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index ce13becdb2..e4a9541855 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -17,11 +17,13 @@ ], "license": "Apache-2.0", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "scripts": { "build": "backstage-cli plugin:build", "lint": "backstage-cli lint", "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 512af5020f..4cb465db14 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -17,11 +17,13 @@ ], "license": "Apache-2.0", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "scripts": { "build": "backstage-cli plugin:build", "lint": "backstage-cli lint", "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/packages/theme/package.json b/packages/theme/package.json index 38392959e8..6c26f2d32b 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -17,10 +17,12 @@ ], "license": "Apache-2.0", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "scripts": { "build": "backstage-cli plugin:build", "lint": "backstage-cli lint", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index b34c85e497..77bc8ed5b0 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -2,7 +2,7 @@ "name": "@backstage/plugin-catalog", "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -11,6 +11,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 0750b2bd09..902fadb6be 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -2,7 +2,7 @@ "name": "@backstage/plugin-explore", "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -11,6 +11,8 @@ "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", "clean": "backstage-cli clean", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "start": "backstage-cli plugin:serve" }, "dependencies": { diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index f031ea643c..6344264147 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -17,13 +17,15 @@ ], "license": "Apache-2.0", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "scripts": { "build": "backstage-cli plugin:build", "start": "backstage-cli plugin:serve", "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json index 015ad1deab..12b4868329 100644 --- a/plugins/home-page/package.json +++ b/plugins/home-page/package.json @@ -2,7 +2,7 @@ "name": "@backstage/plugin-home-page", "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -11,6 +11,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 62487f3f1d..917636e747 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -2,7 +2,7 @@ "name": "@backstage/plugin-lighthouse", "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -11,6 +11,8 @@ "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", "clean": "backstage-cli clean", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "start": "backstage-cli plugin:serve" }, "dependencies": { diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 68822e983b..0962f84a41 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -2,7 +2,7 @@ "name": "@backstage/plugin-register-component", "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -11,6 +11,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index f1c97bbce8..1c596681cc 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -2,12 +2,15 @@ "name": "@backstage/plugin-scaffolder-backend", "version": "0.1.1-alpha.5", "main": "dist", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { "build": "tsc", "lint": "backstage-cli lint", "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 3dc7893ba9..08571713a0 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -2,7 +2,7 @@ "name": "@backstage/plugin-scaffolder", "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -11,6 +11,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean" }, "dependencies": { diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 5e6b90b221..2995cd7f16 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -2,7 +2,7 @@ "name": "@backstage/plugin-tech-radar", "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -10,6 +10,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean", "start": "backstage-cli plugin:serve" }, diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index b9f9156b45..ff086a0b04 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -2,7 +2,7 @@ "name": "@backstage/plugin-welcome", "version": "0.1.1-alpha.5", "main": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "private": true, "license": "Apache-2.0", "scripts": { @@ -10,6 +10,8 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", "clean": "backstage-cli clean", "start": "backstage-cli plugin:serve" }, From 2414267cc05083629cc1ce05a3dbbb57fd8d0164 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 14:25:05 +0200 Subject: [PATCH 67/73] tsconfig: skip declaration map creation --- packages/cli/config/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json index a05e809caa..f905dcb771 100644 --- a/packages/cli/config/tsconfig.json +++ b/packages/cli/config/tsconfig.json @@ -4,7 +4,6 @@ "compilerOptions": { "allowJs": true, "noEmit": false, - "declarationMap": true, "incremental": true, "target": "ES2019", "module": "ESNext", From f0b97f5ff98657b37d69989c885fefb99e755e42 Mon Sep 17 00:00:00 2001 From: nikek Date: Thu, 14 May 2020 15:04:18 +0200 Subject: [PATCH 68/73] make env string 'false' set cors to false --- plugins/catalog-backend/package.json | 1 + plugins/catalog-backend/src/run.ts | 7 +++---- yarn.lock | 5 +++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index c0c8f0b396..629f4c6d18 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -24,6 +24,7 @@ "uuid": "^8.0.0", "winston": "^3.2.1", "yaml": "^1.9.2", + "yn": "^4.0.0", "yup": "^0.28.5" }, "devDependencies": { diff --git a/plugins/catalog-backend/src/run.ts b/plugins/catalog-backend/src/run.ts index 0d5c3a5c12..a2c2601258 100644 --- a/plugins/catalog-backend/src/run.ts +++ b/plugins/catalog-backend/src/run.ts @@ -14,16 +14,15 @@ * limitations under the License. */ +import yn from 'yn'; import { getRootLogger } from '@backstage/backend-common'; import { startStandaloneServer } from './service/standaloneServer'; const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 3003; -const enableCors = process.env.PLUGIN_CORS - ? Boolean(process.env.PLUGIN_CORS) - : false; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); const logger = getRootLogger(); -startStandaloneServer({ port, enableCors, logger }).catch((err) => { +startStandaloneServer({ port, enableCors, logger }).catch(err => { logger.error(err); process.exit(1); }); diff --git a/yarn.lock b/yarn.lock index 0231237bc7..c1316ada9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22537,6 +22537,11 @@ yn@3.1.1: resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== +yn@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yn/-/yn-4.0.0.tgz#611480051ea43b510da1dfdbe177ed159f00a979" + integrity sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg== + yup@^0.28.5: version "0.28.5" resolved "https://registry.npmjs.org/yup/-/yup-0.28.5.tgz#85cabb4000d3623ef69be81551190692e631a8a5" From ebab9274254e30dafe9f83f22b341de72dfb3e7e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 14 May 2020 16:12:05 +0200 Subject: [PATCH 69/73] packages/core: rename api definitions to CamelCaseApi --- .../src/api/apis/definitions/{alert.ts => AlertApi.ts} | 0 .../src/api/apis/definitions/{error.ts => ErrorApi.ts} | 0 .../definitions/{featureFlags.ts => FeatureFlagsApi.ts} | 0 .../definitions/{oauthrequest.ts => OAuthRequestApi.ts} | 0 packages/core/src/api/apis/definitions/index.ts | 8 ++++---- .../OAuthRequestManager/BasicOAuthScopes.ts | 2 +- .../OAuthRequestManager/OAuthPendingRequests.ts | 2 +- .../OAuthRequestManager/OAuthRequestManager.ts | 2 +- packages/core/src/api/app/AppBuilder.tsx | 2 +- packages/core/src/api/app/FeatureFlags.test.tsx | 5 +---- packages/core/src/api/app/FeatureFlags.tsx | 5 +---- 11 files changed, 10 insertions(+), 16 deletions(-) rename packages/core/src/api/apis/definitions/{alert.ts => AlertApi.ts} (100%) rename packages/core/src/api/apis/definitions/{error.ts => ErrorApi.ts} (100%) rename packages/core/src/api/apis/definitions/{featureFlags.ts => FeatureFlagsApi.ts} (100%) rename packages/core/src/api/apis/definitions/{oauthrequest.ts => OAuthRequestApi.ts} (100%) diff --git a/packages/core/src/api/apis/definitions/alert.ts b/packages/core/src/api/apis/definitions/AlertApi.ts similarity index 100% rename from packages/core/src/api/apis/definitions/alert.ts rename to packages/core/src/api/apis/definitions/AlertApi.ts diff --git a/packages/core/src/api/apis/definitions/error.ts b/packages/core/src/api/apis/definitions/ErrorApi.ts similarity index 100% rename from packages/core/src/api/apis/definitions/error.ts rename to packages/core/src/api/apis/definitions/ErrorApi.ts diff --git a/packages/core/src/api/apis/definitions/featureFlags.ts b/packages/core/src/api/apis/definitions/FeatureFlagsApi.ts similarity index 100% rename from packages/core/src/api/apis/definitions/featureFlags.ts rename to packages/core/src/api/apis/definitions/FeatureFlagsApi.ts diff --git a/packages/core/src/api/apis/definitions/oauthrequest.ts b/packages/core/src/api/apis/definitions/OAuthRequestApi.ts similarity index 100% rename from packages/core/src/api/apis/definitions/oauthrequest.ts rename to packages/core/src/api/apis/definitions/OAuthRequestApi.ts diff --git a/packages/core/src/api/apis/definitions/index.ts b/packages/core/src/api/apis/definitions/index.ts index 5d0b8095d8..9e7f7534b9 100644 --- a/packages/core/src/api/apis/definitions/index.ts +++ b/packages/core/src/api/apis/definitions/index.ts @@ -20,7 +20,7 @@ // // If you think some API definition is missing, please open an Issue or send a PR! -export * from './alert'; -export * from './error'; -export * from './featureFlags'; -export * from './oauthrequest'; +export * from './AlertApi'; +export * from './ErrorApi'; +export * from './FeatureFlagsApi'; +export * from './OAuthRequestApi'; diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts index 14aecd1607..fb022c5385 100644 --- a/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { OAuthScopes, OAuthScopeLike } from '../../definitions/oauthrequest'; +import { OAuthScopes, OAuthScopeLike } from '../../definitions'; /** * The BasicOAuthScopes class is an implementation of OAuthScopes that diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts index 904acecf9f..6348262022 100644 --- a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts @@ -15,7 +15,7 @@ */ import Observable from 'zen-observable'; -import { OAuthScopes } from '../../definitions/oauthrequest'; +import { OAuthScopes } from '../../definitions'; type RequestQueueEntry = { scopes: OAuthScopes; diff --git a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts index dd31a4b33c..02d89b9546 100644 --- a/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts @@ -20,7 +20,7 @@ import { PendingAuthRequest, AuthRequester, AuthRequesterOptions, -} from '../../definitions/oauthrequest'; +} from '../../definitions'; import Observable from 'zen-observable'; import { OAuthPendingRequests, PendingRequest } from './OAuthPendingRequests'; diff --git a/packages/core/src/api/app/AppBuilder.tsx b/packages/core/src/api/app/AppBuilder.tsx index f9055a0bf7..a375593cdb 100644 --- a/packages/core/src/api/app/AppBuilder.tsx +++ b/packages/core/src/api/app/AppBuilder.tsx @@ -20,7 +20,7 @@ import { AppContextProvider } from './AppContext'; import { App } from './types'; import { BackstagePlugin } from '../plugin'; import { FeatureFlagsRegistryItem } from './FeatureFlags'; -import { featureFlagsApiRef } from '../apis/definitions/featureFlags'; +import { featureFlagsApiRef } from '../apis/definitions'; import ErrorPage from '../../layout/ErrorPage'; import { diff --git a/packages/core/src/api/app/FeatureFlags.test.tsx b/packages/core/src/api/app/FeatureFlags.test.tsx index 5822d05be8..ca4a819f13 100644 --- a/packages/core/src/api/app/FeatureFlags.test.tsx +++ b/packages/core/src/api/app/FeatureFlags.test.tsx @@ -15,10 +15,7 @@ */ import { FeatureFlags as FeatureFlagsImpl } from './FeatureFlags'; -import { - FeatureFlagState, - FeatureFlagsApi, -} from '../apis/definitions/featureFlags'; +import { FeatureFlagState, FeatureFlagsApi } from '../apis/definitions'; describe('FeatureFlags', () => { beforeEach(() => { diff --git a/packages/core/src/api/app/FeatureFlags.tsx b/packages/core/src/api/app/FeatureFlags.tsx index 63c1b19d88..6af10f228e 100644 --- a/packages/core/src/api/app/FeatureFlags.tsx +++ b/packages/core/src/api/app/FeatureFlags.tsx @@ -15,10 +15,7 @@ */ import { FeatureFlagName } from '../plugin/types'; -import { - FeatureFlagState, - FeatureFlagsApi, -} from '../apis/definitions/featureFlags'; +import { FeatureFlagState, FeatureFlagsApi } from '../apis/definitions'; /** * Helper method for validating compatibility and flag name. From f6d9b18a4e047eb2380609fc50d848866f5cfbbe Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Thu, 14 May 2020 17:54:51 +0200 Subject: [PATCH 70/73] fix: add blog post for tech radar (#870) --- plugins/tech-radar/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md index 49a5bb3add..fb9d220b8c 100644 --- a/plugins/tech-radar/README.md +++ b/plugins/tech-radar/README.md @@ -4,6 +4,8 @@ The Backstage integration for the Tech Radar based on [Zalando's Tech Radar](https://opensource.zalando.com/tech-radar/) open sourced on [GitHub](https://github.com/zalando/tech-radar). This is used at [Spotify](https://spotify.github.io) for visualizing the official guidelines of different areas of software development such as languages, frameworks, infrastructure and processes. +Read the [blog post on backstage.io about the Tech Radar](https://backstage.io/blog/2020/05/14/tech-radar-plugin). + ## Purpose Zalando has a fantastic description [on their website](https://opensource.zalando.com/tech-radar/): From 6e4d7866ce2d0bc227c980ddf5af9f8860331208 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 15 May 2020 08:36:04 +0200 Subject: [PATCH 71/73] build(deps): bump @rollup/plugin-json from 4.0.2 to 4.0.3 (#874) Bumps [@rollup/plugin-json](https://github.com/rollup/plugins) from 4.0.2 to 4.0.3. - [Release notes](https://github.com/rollup/plugins/releases) - [Commits](https://github.com/rollup/plugins/compare/url-v4.0.2...json-v4.0.3) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index c1316ada9c..f245e5b4cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2773,11 +2773,11 @@ resolve "^1.11.0" "@rollup/plugin-json@^4.0.2": - version "4.0.2" - resolved "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.0.2.tgz#482185ee36ac7dd21c346e2dbcc22ffed0c6f2d6" - integrity sha512-t4zJMc98BdH42mBuzjhQA7dKh0t4vMJlUka6Fz0c+iO5IVnWaEMiYBy1uBj9ruHZzXBW23IPDGL9oCzBkQ9Udg== + version "4.0.3" + resolved "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.0.3.tgz#747e2c2884c5a0fa00b66c9c0f3f1012cddca534" + integrity sha512-QMUT0HZNf4CX17LMdwaslzlYHUKTYGuuk34yYIgZrNdu+pMEfqMS55gck7HEeHBKXHM4cz5Dg1OVwythDdbbuQ== dependencies: - "@rollup/pluginutils" "^3.0.4" + "@rollup/pluginutils" "^3.0.8" "@rollup/plugin-node-resolve@^7.1.1": version "7.1.3" @@ -2790,7 +2790,7 @@ is-module "^1.0.0" resolve "^1.14.2" -"@rollup/pluginutils@^3.0.0", "@rollup/pluginutils@^3.0.4", "@rollup/pluginutils@^3.0.8": +"@rollup/pluginutils@^3.0.0", "@rollup/pluginutils@^3.0.8": version "3.0.10" resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.10.tgz#a659b9025920378494cd8f8c59fbf9b3a50d5f12" integrity sha512-d44M7t+PjmMrASHbhgpSbVgtL6EFyX7J4mYxwQ/c5eoaE6N2VgCgEcWVzNnwycIloti+/MpwFr8qfw+nRw00sw== From 0f57dd2aacbcfc3e65be3170828cc0b979fb1107 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 15 May 2020 08:54:56 +0200 Subject: [PATCH 72/73] build(deps): bump rc-progress from 2.5.3 to 3.0.0 (#872) Bumps [rc-progress](https://github.com/react-component/progress) from 2.5.3 to 3.0.0. - [Release notes](https://github.com/react-component/progress/releases) - [Changelog](https://github.com/react-component/progress/blob/master/HISTORY.md) - [Commits](https://github.com/react-component/progress/compare/2.5.3...3.0.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- packages/core/package.json | 2 +- yarn.lock | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 2a2c436003..295b95050c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,7 +35,7 @@ "lodash": "^4.17.15", "material-table": "^1.57.2", "prop-types": "^15.7.2", - "rc-progress": "^2.5.2", + "rc-progress": "^3.0.0", "react": "^16.12.0", "react-addons-text-content": "0.0.4", "react-dom": "^16.12.0", diff --git a/yarn.lock b/yarn.lock index f245e5b4cc..63365ffbf3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6018,7 +6018,7 @@ babel-preset-react-app@^9.1.2: babel-plugin-macros "2.8.0" babel-plugin-transform-react-remove-prop-types "0.4.24" -babel-runtime@6.26.0, babel-runtime@6.x, babel-runtime@^6.23.0, babel-runtime@^6.26.0: +babel-runtime@6.26.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= @@ -17516,7 +17516,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -17775,13 +17775,12 @@ raw-loader@^3.1.0: loader-utils "^1.1.0" schema-utils "^2.0.1" -rc-progress@^2.5.2: - version "2.5.3" - resolved "https://registry.npmjs.org/rc-progress/-/rc-progress-2.5.3.tgz#00f01b95bdbe1856d3a5f82242051902e8b7a8e7" - integrity sha512-K2fa4CnqGehLZoMrdmBeZ86ONSTVcdk5FlqetbwJ3R/+42XfqhwQVOjWp2MH4P7XSQOMAGcNOy1SFfCP3415sg== +rc-progress@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/rc-progress/-/rc-progress-3.0.0.tgz#cea324ce8fc31421cd815d94a4649a8a29f8f8db" + integrity sha512-dQv1KU3o6Vay604FMYMF4S0x4GNXAgXf1tbQ1QoxeIeQt4d5fUeB7Ri82YPu+G+aRvH/AtxYAlEcnxyVZ1/4Hw== dependencies: - babel-runtime "6.x" - prop-types "^15.5.8" + classnames "^2.2.6" rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: version "1.2.8" From b16ad78bee48f576c7050e74fdddeb1146b930c5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 15 May 2020 08:57:18 +0200 Subject: [PATCH 73/73] build(deps-dev): bump tsc-watch from 4.2.3 to 4.2.5 (#873) Bumps [tsc-watch](https://github.com/gilamran/tsc-watch) from 4.2.3 to 4.2.5. - [Release notes](https://github.com/gilamran/tsc-watch/releases) - [Changelog](https://github.com/gilamran/tsc-watch/blob/master/CHANGELOG.md) - [Commits](https://github.com/gilamran/tsc-watch/commits) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 63365ffbf3..08a530db89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20994,9 +20994,9 @@ ts-pnp@1.1.6, ts-pnp@^1.1.2, ts-pnp@^1.1.6: integrity sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ== tsc-watch@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.2.3.tgz#630aaf1ed8bbfb6fbcbddb9bef96054bed217c4b" - integrity sha512-M1Lo37+ggVfavGX3ObUVMz9QBH7moqq2RlmBdxnz6a6etwecetznZ/ZgYOi2c9HQ4Ki2qStj7V9J/gSf0rThig== + version "4.2.5" + resolved "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.2.5.tgz#3ee680cfd02087bb76cbecd2d5f290a0d6302454" + integrity sha512-scXpL5SFJevvtKOtQIRxJvyEwCKSIZS9bDrO/cy/dWlETTVwSlfRm2WmakdRPM0Rg90DbYyJjkUr2z74HXcxzQ== dependencies: cross-spawn "^5.1.0" node-cleanup "^2.1.2"