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: diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 15fb2b1fc7..c19583b653 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -48,18 +48,31 @@ 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 + - name: verify plugin template + run: yarn lerna -- run diff -- --check + - name: lint run: yarn lerna -- run lint --since origin/master - 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 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 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 diff --git a/docs/contributorheader.png b/docs/contributorheader.png new file mode 100644 index 0000000000..53d1027d85 Binary files /dev/null and b/docs/contributorheader.png differ 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/package.json b/package.json index 6459abcc5e..b3a02bb388 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 --", diff --git a/packages/app/package.json b/packages/app/package.json index 47f553a98b..e0c534ea45 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,19 +1,20 @@ { "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/cli": "^0.1.1-alpha.5", + "@backstage/core": "^0.1.1-alpha.5", "@backstage/plugin-circleci": "^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-lighthouse": "^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/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", @@ -21,8 +22,8 @@ "prop-types": "^15.7.2", "react": "^16.12.0", "react-dom": "^16.12.0", - "react-router-dom": "^5.1.2", - "react-use": "^13.24.0", + "react-router-dom": "^5.2.0", + "react-use": "^14.2.0", "zen-observable": "^0.8.15" }, "devDependencies": { @@ -30,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/jquery": "^3.3.34", "@types/node": "^12.0.0", "@types/react-router-dom": "^5.1.3", 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 463d5dc0d1..021005a97a 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -16,8 +16,9 @@ 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'; export { plugin as Circleci } from '@backstage/plugin-circleci'; +export { plugin as RegisterComponent } from '@backstage/plugin-register-component'; diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index eec21063ce..27ef241f3d 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,8 +1,9 @@ { "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", + "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": { @@ -29,17 +32,18 @@ "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", "@types/supertest": "^2.0.8", + "@types/yaml": "^1.9.7", "get-port": "^5.1.1", "http-errors": "^1.7.3", "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/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..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", @@ -12,27 +12,31 @@ "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", - "@backstage/plugin-inventory-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", - "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", + "@backstage/cli": "^0.1.1-alpha.5", "@types/compression": "^1.7.0", "@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.8.3", + "typescript": "^3.9.2", "winston": "^3.2.1" }, "nodemonConfig": { diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 31b53f6d30..8a7a4c9b72 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -28,19 +28,30 @@ 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 knex from 'knex'; +import catalog from './plugins/catalog'; +import scaffolder from './plugins/scaffolder'; 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' }), -}; + +function createEnv(plugin: string): PluginEnvironment { + 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() { const app = express(); @@ -50,8 +61,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('/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/catalog.ts b/packages/backend/src/plugins/catalog.ts new file mode 100644 index 0000000000..fb41e01692 --- /dev/null +++ b/packages/backend/src/plugins/catalog.ts @@ -0,0 +1,26 @@ +/* + * 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 { + DatabaseCatalog, + createRouter, +} from '@backstage/plugin-catalog-backend'; +import { PluginEnvironment } from '../types'; + +export default async function ({ logger, database }: PluginEnvironment) { + const catalog = await DatabaseCatalog.create(database, logger); + return await createRouter({ catalog, 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/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", diff --git a/packages/cli/package.json b/packages/cli/package.json index ee46b09862..7183320136 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" @@ -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", @@ -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", @@ -55,14 +55,14 @@ "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", - "rollup-plugin-postcss": "^2.5.0", + "rollup-plugin-postcss": "^3.1.1", "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" }, @@ -89,7 +89,7 @@ "templates", "config", "bin", - "dist" + "dist/**/*.js" ], "nodemonConfig": { "watch": "./src", 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}`, + ); + }); + }, + ); } } diff --git a/packages/backend/src/plugins/inventory.ts b/packages/cli/src/commands/pack.ts similarity index 52% rename from packages/backend/src/plugins/inventory.ts rename to packages/cli/src/commands/pack.ts index d48a62b5fe..ba9c7725ae 100644 --- a/packages/backend/src/plugins/inventory.ts +++ b/packages/cli/src/commands/pack.ts @@ -14,23 +14,21 @@ * limitations under the License. */ -import { - AggregatorInventory, - createRouter, - StaticInventory, -} from '@backstage/plugin-inventory-backend'; -import type { PluginEnvironment } from '../types'; +import fs from 'fs-extra'; +import { paths } from '../lib/paths'; -export default async function ({ logger }: PluginEnvironment) { - const inventory = new AggregatorInventory(); - inventory.enlist( - new StaticInventory([ - { id: 'component1' }, - { id: 'component2' }, - { id: 'component3' }, - { id: 'component4' }, - ]), - ); +export const pre = async () => { + const pkgPath = paths.resolveTarget('package.json'); - return await createRouter({ inventory, logger }); -} + 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/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/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/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" } } 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..5596eb9151 100644 --- a/packages/cli/templates/default-app/packages/app/package.json.hbs +++ b/packages/cli/templates/default-app/packages/app/package.json.hbs @@ -12,14 +12,14 @@ "plugin-welcome": "0.0.0", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-router-dom": "^5.1.2", - "react-use": "^13.24.0" + "react-router-dom": "^5.2.0", + "react-use": "^14.2.0" }, "devDependencies": { "@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-app/plugins/welcome/package.json.hbs b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs index 13fe34f682..c814473826 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": { @@ -19,8 +21,8 @@ "@material-ui/icons": "^4.9.1", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^13.24.0", - "react-router-dom": "5.1.2" + "react-use": "^14.2.0", + "react-router-dom": "^5.2.0" }, "devDependencies": { "@backstage/cli": "^{{version}}", @@ -31,6 +33,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..0a0ecc7e76 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -2,15 +2,20 @@ "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, + "publishConfig": { + "access": "public" + }, "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": { @@ -21,7 +26,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}}", @@ -29,12 +34,12 @@ "@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" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/packages/core/package.json b/packages/core/package.json index 5918fd6008..295b95050c 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" @@ -17,15 +17,17 @@ ], "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": { - "@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", @@ -33,30 +35,34 @@ "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", "react-helmet": "5.2.1", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2", - "react-sparklines": "^1.7.0" + "react-router": "^5.2.0", + "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": { - "@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", "@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" + "@types/react-sparklines": "^1.7.0", + "@types/zen-observable": "^0.8.0" }, "files": [ - "dist" + "dist/**/*.{js,d.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/AlertApi.ts similarity index 96% rename from packages/core/src/api/apis/definitions/alert.ts rename to packages/core/src/api/apis/definitions/AlertApi.ts index 133255c719..e5d7001b56 100644 --- a/packages/core/src/api/apis/definitions/alert.ts +++ b/packages/core/src/api/apis/definitions/AlertApi.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/ErrorApi.ts similarity index 98% rename from packages/core/src/api/apis/definitions/error.ts rename to packages/core/src/api/apis/definitions/ErrorApi.ts index f2a8509558..7d9816426e 100644 --- a/packages/core/src/api/apis/definitions/error.ts +++ b/packages/core/src/api/apis/definitions/ErrorApi.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/FeatureFlagsApi.ts similarity index 98% rename from packages/core/src/api/apis/definitions/featureFlags.ts rename to packages/core/src/api/apis/definitions/FeatureFlagsApi.ts index 926fe091d5..8636c1f5c1 100644 --- a/packages/core/src/api/apis/definitions/featureFlags.ts +++ b/packages/core/src/api/apis/definitions/FeatureFlagsApi.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/definitions/OAuthRequestApi.ts b/packages/core/src/api/apis/definitions/OAuthRequestApi.ts new file mode 100644 index 0000000000..09e5f77718 --- /dev/null +++ b/packages/core/src/api/apis/definitions/OAuthRequestApi.ts @@ -0,0 +1,185 @@ +/* + * 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 shown to the user so that they can be informed about what login is being requested + * before a popup is shown. + */ +export type AuthProvider = { + /** + * Title for the auth provider, for example "GitHub" + */ + title: string; + + /** + * Icon for the auth provider. + */ + icon: IconComponent; +}; + +/** + * 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. + */ + provider: AuthProvider; + + /** + * Implementation of the auth flow, which will be called synchronously when + * trigger() is called on an auth requests. + */ + 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 onAuthRequest in the + * AuthRequesterOptions. Or rejected, if the request is rejected. + * + * This function can be called multiple times before the promise resolves. All calls + * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the + * union of all requested scopes. + */ +export type AuthRequester = ( + scope: OAuthScopes, +) => Promise; + +/** + * An pending auth request for a single auth provider. The request will remain in this pending + * state until either reject() or trigger() is called. + * + * Any new requests for the same provider are merged into the existing pending request, meaning + * there will only ever be a single pending request for a given provider. + */ +export type PendingAuthRequest = { + /** + * Information about the auth provider, as given in the AuthRequesterOptions + */ + provider: AuthProvider; + + /** + * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". + */ + reject: () => void; + + /** + * Trigger the auth request to continue the auth flow, by for example showing a popup. + * + * Synchronously calls onAuthRequest with all scope currently in the request. + */ + trigger(): Promise; +}; + +/** + * 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. + * + * The returned promise resolves to the contents of the message that was posted from the auth popup. + */ + 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 to handle auth requests. + * + * The returned AuthRequester function is used to request login with new scopes. These requests + * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests + * triggers an auth flow. + * + * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. + */ + createAuthRequester( + options: AuthRequesterOptions, + ): AuthRequester; + + /** + * Observers panding auth requests. The returned observable will emit all + * current active auth request, at most one for each created auth requester. + * + * Each request has its own info about the login provider, forwarded from the auth requester options. + * + * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. + * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". + * If a auth is triggered, and the auth handler resolves successfully, then all currently pending + * AuthRequester calls will resolve to the value returned by the onAuthRequest call. + */ + authRequest$(): Observable; +}; + +export const oauthRequestApiRef = new ApiRef({ + id: 'core.oauthrequest', + description: 'An API for implementing unified OAuth flows in Backstage', +}); diff --git a/packages/core/src/api/apis/definitions/index.ts b/packages/core/src/api/apis/definitions/index.ts index ca1290403f..9e7f7534b9 100644 --- a/packages/core/src/api/apis/definitions/index.ts +++ b/packages/core/src/api/apis/definitions/index.ts @@ -20,6 +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 './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 new file mode 100644 index 0000000000..fb022c5385 --- /dev/null +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/BasicOAuthScopes.ts @@ -0,0 +1,77 @@ +/* + * 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'; + +/** + * 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); + 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..6348262022 --- /dev/null +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthPendingRequests.ts @@ -0,0 +1,116 @@ +/* + * 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'; + +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>; +}; + +/** + * The OAuthPendingRequests class is a utility for managing and observing + * a stream of requests for oauth scopes for a single provider, and resolving + * them correctly once requests are fulfilled. + */ +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..02d89b9546 --- /dev/null +++ b/packages/core/src/api/apis/implementations/OAuthRequestManager/OAuthRequestManager.ts @@ -0,0 +1,165 @@ +/* + * 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, + PendingAuthRequest, + AuthRequester, + AuthRequesterOptions, +} from '../../definitions'; +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 request for each OAuth provider. + */ +export class OAuthRequestManager implements OAuthRequestApi { + private readonly request$: Observable; + private readonly observers = new Set< + ZenObservable.SubscriptionObserver + >(); + private currentRequests: PendingAuthRequest[] = []; + private handlerCount = 0; + + constructor() { + this.request$ = 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, + ): PendingAuthRequest | undefined { + const { scopes } = request; + if (!scopes) { + return undefined; + } + + return { + provider: options.provider, + trigger: async () => { + const result = await options.onAuthRequest(scopes); + request.resolve(result); + }, + reject: () => { + const error = new Error('Login failed, rejected by user'); + error.name = 'RejectedError'; + request.reject(error); + }, + }; + } + + authRequest$(): Observable { + return this.request$; + } + + 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'; 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 f65482b996..a375593cdb 100644 --- a/packages/core/src/api/app/AppBuilder.tsx +++ b/packages/core/src/api/app/AppBuilder.tsx @@ -18,9 +18,9 @@ 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 { featureFlagsApiRef } from '../apis/definitions'; import ErrorPage from '../../layout/ErrorPage'; import { @@ -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(); @@ -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; @@ -132,3 +145,7 @@ export default class AppBuilder { return () => ; } } + +export function createApp() { + return new AppBuilder(); +} 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. diff --git a/packages/core/src/api/app/index.ts b/packages/core/src/api/app/index.ts new file mode 100644 index 0000000000..156c9870b8 --- /dev/null +++ b/packages/core/src/api/app/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { 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 e60a7efa18..2eabc38280 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -export * from './api'; export * from './apis'; -export { FeatureFlags } from './app/FeatureFlags'; -export { useApp } from './app/AppContext'; +export * from './app'; +export * from './navTargets'; +export * from './plugin'; +export * from './types'; 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..b090af277a --- /dev/null +++ b/packages/core/src/api/navTargets/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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; +}; diff --git a/packages/core/src/api/plugin/Plugin.tsx b/packages/core/src/api/plugin/Plugin.tsx index 3d067c66ad..74aeaaa523 100644 --- a/packages/core/src/api/plugin/Plugin.tsx +++ b/packages/core/src/api/plugin/Plugin.tsx @@ -20,8 +20,10 @@ import { RoutePath, RouteOptions, FeatureFlagName, + BackstagePlugin, } from './types'; import { validateBrowserCompat, validateFlagName } from '../app/FeatureFlags'; +import { NavTarget } from '../navTargets'; export type PluginConfig = { id: string; @@ -34,6 +36,12 @@ export type PluginHooks = { }; export type RouterHooks = { + addRoute( + target: NavTarget, + Component: ComponentType, + options?: RouteOptions, + ): void; + registerRoute( path: RoutePath, Component: ComponentType, @@ -51,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) {} @@ -75,6 +80,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 }); }, @@ -99,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/plugins/inventory-backend/src/inventory/index.ts b/packages/core/src/api/plugin/index.ts similarity index 89% rename from plugins/inventory-backend/src/inventory/index.ts rename to packages/core/src/api/plugin/index.ts index 3affa1109a..79b0575755 100644 --- a/plugins/inventory-backend/src/inventory/index.ts +++ b/packages/core/src/api/plugin/index.ts @@ -14,6 +14,5 @@ * limitations under the License. */ -export * from './AggregatorInventory'; -export * from './StaticInventory'; +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 c4ac426e5c..859813356a 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,11 @@ export type FeatureFlagOutput = { export type PluginOutput = | RouteOutput + | RouteTargetOutput | RedirectRouteOutput | FeatureFlagOutput; + +export type BackstagePlugin = { + getId(): string; + output(): PluginOutput[]; +}; diff --git a/packages/core/src/api/types.ts b/packages/core/src/api/types.ts new file mode 100644 index 0000000000..09f0bc11ae --- /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; +}; 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..fc7c262882 --- /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/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx new file mode 100644 index 0000000000..8e3d98c998 --- /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 { PendingAuthRequest } from '../../api'; + +const useItemStyles = makeStyles((theme) => ({ + root: { + paddingLeft: theme.spacing(3), + }, +})); + +type RowProps = { + request: PendingAuthRequest; + 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.trigger(); + } catch (e) { + setError(e); + } finally { + setBusy(false); + } + }; + + const IconComponent = request.provider.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..617cce2aee --- /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.authRequest$(), [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/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..9d8450293f 100644 --- a/packages/core/src/components/Status/Status.tsx +++ b/packages/core/src/components/Status/Status.tsx @@ -21,79 +21,67 @@ 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, + }, }, - '@keyframes blink': { - '50%': { - backgroundColor: theme.palette.status.background, + aborted: { + '&::before': { + backgroundColor: theme.palette.status.aborted, }, }, })); 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 +104,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/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'; 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; diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index bafbab2e32..aea4f9b6ae 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" @@ -17,31 +17,33 @@ ], "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": { - "@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", "@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", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2" + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } 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; } 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 33c27dba62..8509763333 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" @@ -17,22 +17,24 @@ ], "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": { "@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" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index e34a750ae0..0367036610 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" @@ -17,29 +17,31 @@ ], "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": { - "@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": "^24.0.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.1.2", - "react-router-dom": "^5.1.2" + "react-router": "^5.2.0", + "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 18b4dd7fa2..be43eb616a 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" @@ -17,19 +17,21 @@ ], "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": { "@material-ui/core": "^4.9.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4" + "@backstage/cli": "^0.1.1-alpha.5" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts index f2c26e6e4a..33cefe3283 100644 --- a/packages/theme/src/themes.ts +++ b/packages/theme/src/themes.ts @@ -23,12 +23,12 @@ export const lightTheme = createTheme({ default: '#F8F8F8', }, status: { - ok: '#1db855', - warning: '#f49b20', - error: '#CA001B', - running: '#BEBEBE', - pending: '#5BC0DE', - background: '#F8F8F8', + ok: '#1DB954', + warning: '#FF9800', + error: '#E22134', + running: '#2E77D0', + pending: '#FFED51', + aborted: '#757575', }, bursts: { fontColor: '#FEFEFE', @@ -63,12 +63,12 @@ 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', }, bursts: { fontColor: '#FEFEFE', diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index c020fbb4e1..82c717e433 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -27,7 +27,7 @@ type PaletteAdditions = { error: string; pending: string; running: string; - background: string; + aborted: string; }; border: string; textContrast: string; 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/catalog-backend/fixtures/component1.yaml b/plugins/catalog-backend/fixtures/component1.yaml new file mode 100644 index 0000000000..b0b54e0902 --- /dev/null +++ b/plugins/catalog-backend/fixtures/component1.yaml @@ -0,0 +1,6 @@ +apiVersion: catalog.backstage.io/v1 +kind: Component +metadata: + name: component1 +spec: + type: service diff --git a/plugins/inventory-backend/package.json b/plugins/catalog-backend/package.json similarity index 63% rename from plugins/inventory-backend/package.json rename to plugins/catalog-backend/package.json index 9a72daed5e..629f4c6d18 100644 --- a/plugins/inventory-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { - "name": "@backstage/plugin-inventory-backend", - "version": "0.1.1-alpha.4", + "name": "@backstage/plugin-catalog-backend", + "version": "0.1.1-alpha.5", "main": "dist", "license": "Apache-2.0", "private": true, @@ -12,17 +12,25 @@ "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", "express-promise-router": "^3.0.3", + "fs-extra": "^9.0.0", "helmet": "^3.22.0", + "knex": "^0.21.1", "morgan": "^1.10.0", - "winston": "^3.2.1" + "uuid": "^8.0.0", + "winston": "^3.2.1", + "yaml": "^1.9.2", + "yn": "^4.0.0", + "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", "tsc-watch": "^4.2.3" }, 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..5234136b55 --- /dev/null +++ b/plugins/catalog-backend/src/catalog/CatalogLogic.test.ts @@ -0,0 +1,75 @@ +/* + * 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().mockResolvedValue([]), + } 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().mockResolvedValue([ + { + 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, + '123', + 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..686aba0c40 --- /dev/null +++ b/plugins/catalog-backend/src/catalog/CatalogLogic.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { 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 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 Promise.race([ + new Promise((resolve) => setTimeout(resolve, 10000)), + cancellationPromise, + ]); + } + }; + startRefresh(); + + return cancel!; + } + + 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 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 new file mode 100644 index 0000000000..98a5e9acd4 --- /dev/null +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.test.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. + */ + +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, + }); + 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() })], + }); + + beforeEach(async () => { + await database.migrate.latest({ + directory: path.resolve(__dirname, '..', 'migrations'), + loadExtensions: ['.ts'], + }); + }); + + it('manages locations', async () => { + const catalog = new DatabaseCatalog(database, logger); + const input = { type: 'a', target: 'b' }; + const output = { id: expect.anything(), type: 'a', target: 'b' }; + + 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.toThrow( + /Found no location/, + ); + }); +}); diff --git a/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts new file mode 100644 index 0000000000..87ff5a503f --- /dev/null +++ b/plugins/catalog-backend/src/catalog/DatabaseCatalog.ts @@ -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 { NotFoundError } from '@backstage/backend-common'; +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'; + +export class DatabaseCatalog implements Catalog { + static async create( + database: Knex, + logger: Logger, + ): Promise { + await database.migrate.latest({ + directory: path.resolve(__dirname, '..', 'migrations'), + loadExtensions: ['.js'], + }); + + const databaseCatalog = new DatabaseCatalog(database, logger); + CatalogLogic.startRefreshLoop(databaseCatalog, readLocation, logger); + + return databaseCatalog; + } + + constructor( + private readonly database: Knex, + private readonly logger: Logger, + ) {} + + async addOrUpdateComponent( + locationId: string, + descriptor: ComponentDescriptor, + ): Promise { + const component: Component = { + name: descriptor.metadata.name, + }; + + await this.database.transaction(async (tx) => { + // 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, + }); + } + }); + } + + async components(): Promise { + return await this.database('components').select(); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async component(name: string): Promise { + 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 { + 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/catalog-backend/src/catalog/StaticCatalog.ts b/plugins/catalog-backend/src/catalog/StaticCatalog.ts new file mode 100644 index 0000000000..28d6cff9a3 --- /dev/null +++ b/plugins/catalog-backend/src/catalog/StaticCatalog.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 { 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 { + private _components: Component[]; + private _locations: Location[]; + + constructor(components: Component[], locations: Location[]) { + this._components = components; + this._locations = locations; + } + + async addOrUpdateComponent( + locationId: string, + descriptor: ComponentDescriptor, + ): Promise { + this._components = this._components + .filter((c) => c.name !== descriptor.metadata.name) + .concat([{ name: descriptor.metadata.name }]); + } + + async components(): Promise { + return this._components.slice(); + } + + async component(name: string): Promise { + const item = this._components.find((i) => i.name === name); + if (!item) { + throw new NotFoundError(`Found no component with name ${name}`); + } + 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/catalog-backend/src/catalog/index.ts b/plugins/catalog-backend/src/catalog/index.ts new file mode 100644 index 0000000000..b4bc07c6bb --- /dev/null +++ b/plugins/catalog-backend/src/catalog/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 './DatabaseCatalog'; +export * from './StaticCatalog'; +export * from './types'; diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts new file mode 100644 index 0000000000..3a7b685003 --- /dev/null +++ b/plugins/catalog-backend/src/catalog/types.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 * as yup from 'yup'; +import { ComponentDescriptor } from '../descriptors'; + +export type Component = { + name: 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 Catalog = { + addOrUpdateComponent( + locationId: string, + descriptor: ComponentDescriptor, + ): Promise; + components(): Promise; + component(id: string): Promise; + addLocation(location: AddLocationRequest): Promise; + removeLocation(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 new file mode 100644 index 0000000000..abe75a0050 --- /dev/null +++ b/plugins/catalog-backend/src/descriptors/component.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as yup from 'yup'; +import { DescriptorEnvelope } from './envelope'; + +export type ComponentDescriptor = { + metadata: { + name: string; + }; + spec: { + type: string; + }; +}; + +const componentDescriptorSchema: yup.Schema = yup.object({ + kind: yup + .string() + .required() + .matches(/^Component$/), + 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}`); + } + + return [componentDescriptor]; +} 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..20118c7d97 --- /dev/null +++ b/plugins/catalog-backend/src/descriptors/util.ts @@ -0,0 +1,26 @@ +/* + * 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 { ComponentDescriptor, 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/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/catalog-backend/src/ingestion/fileLocation.ts b/plugins/catalog-backend/src/ingestion/fileLocation.ts new file mode 100644 index 0000000000..e856e701d3 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/fileLocation.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 fs from 'fs-extra'; +import { ComponentDescriptor, parseDescriptor } from '../descriptors'; + +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..e96be6822a --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/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 './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..426188ed2c --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Location } from '../catalog'; +import { ComponentDescriptor } from '../descriptors'; + +export type LocationReader = ( + location: Location, +) => Promise; diff --git a/packages/core/src/api/api.ts b/plugins/catalog-backend/src/ingestion/util.ts similarity index 58% rename from packages/core/src/api/api.ts rename to plugins/catalog-backend/src/ingestion/util.ts index cafd0af126..ede6b1b21a 100644 --- a/packages/core/src/api/api.ts +++ b/plugins/catalog-backend/src/ingestion/util.ts @@ -14,18 +14,17 @@ * limitations under the License. */ -import ApiRef, { ApiRefConfig } from './apis/ApiRef'; -import AppBuilder from './app/AppBuilder'; -import BackstagePlugin, { PluginConfig } from './plugin/Plugin'; +import { Location } from '../catalog/types'; +import { ComponentDescriptor } from '../descriptors'; +import { readFileLocation } from './fileLocation'; -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 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/plugins/catalog-backend/src/migrations/20200511113813_init.ts b/plugins/catalog-backend/src/migrations/20200511113813_init.ts new file mode 100644 index 0000000000..4c71fefddd --- /dev/null +++ b/plugins/catalog-backend/src/migrations/20200511113813_init.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * 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 + .string('target') + .notNullable() + .comment('The actual target of the location'); + }) + .createTable('components', (table) => { + 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'); + }); +} + +export async function down(knex: Knex): Promise { + return knex.schema.dropTable('components').dropTable('locations'); +} diff --git a/plugins/inventory-backend/src/run.ts b/plugins/catalog-backend/src/run.ts similarity index 91% rename from plugins/inventory-backend/src/run.ts rename to plugins/catalog-backend/src/run.ts index 0d5c3a5c12..0a684c379e 100644 --- a/plugins/inventory-backend/src/run.ts +++ b/plugins/catalog-backend/src/run.ts @@ -14,13 +14,12 @@ * 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) => { diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts new file mode 100644 index 0000000000..3bdbedb766 --- /dev/null +++ b/plugins/catalog-backend/src/service/router.ts @@ -0,0 +1,100 @@ +/* + * 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 { InputError } from '@backstage/backend-common'; +import express, { Request } from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; +import yup from 'yup'; +import { addLocationRequestShape, Catalog } from '../catalog'; + +export interface RouterOptions { + catalog: Catalog; + 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 catalog = options.catalog; + const logger = options.logger.child({ plugin: 'catalog' }); + const router = Router(); + + // Components + router + .get('/components', async (req, res) => { + const components = await catalog.components(); + res.status(200).send(components); + }) + .get('/components/:id', async (req, res) => { + const { id } = req.params; + const component = await catalog.component(id); + res.status(200).send(component); + }); + + // Locations + router + .post('/locations', async (req, res) => { + const input = await validateRequestBody(req, addLocationRequestShape); + const output = await catalog.addLocation(input); + res.status(201).send(output); + }) + .get('/locations', async (req, res) => { + const output = await catalog.locations(); + res.status(200).send(output); + }) + .get('/locations/:id', async (req, res) => { + const { id } = req.params; + const output = await catalog.location(id); + res.status(200).send(output); + }) + .delete('/locations/:id', async (req, res) => { + const { id } = req.params; + await catalog.removeLocation(id); + res.status(200).send(); + }); + + const app = express(); + app.set('logger', logger); + app.use('/', router); + + return app; +} 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 79% rename from plugins/inventory-backend/src/service/standaloneServer.ts rename to plugins/catalog-backend/src/service/standaloneServer.ts index be339c5ecc..d5c46d7293 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 { AggregatorInventory, StaticInventory } from '../inventory'; +import { StaticCatalog } from '../catalog'; import { createStandaloneApplication } from './standaloneApplication'; export interface ServerOptions { @@ -28,22 +28,22 @@ 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 AggregatorInventory(); - inventory.enlist( - new StaticInventory([ - { id: 'component1' }, - { id: 'component2' }, - { id: 'component3' }, - { id: 'component4' }, - ]), + const catalog = new StaticCatalog( + [ + { name: 'component1' }, + { name: 'component2' }, + { name: 'component3' }, + { name: 'component4' }, + ], + [], ); 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 92% rename from plugins/inventory-backend/tsconfig.json rename to plugins/catalog-backend/tsconfig.json index dd13cfe1bc..015a967f76 100644 --- a/plugins/inventory-backend/tsconfig.json +++ b/plugins/catalog-backend/tsconfig.json @@ -6,7 +6,7 @@ "sourceMap": true, "declaration": true, "strict": true, - "target": "es5", + "target": "es2019", "module": "commonjs", "esModuleInterop": true, "lib": ["es2019"], 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-backend/src/inventory/types.ts b/plugins/catalog/dev/index.tsx similarity index 80% rename from plugins/inventory-backend/src/inventory/types.ts rename to plugins/catalog/dev/index.tsx index 863dccb291..812a5585d4 100644 --- a/plugins/inventory-backend/src/inventory/types.ts +++ b/plugins/catalog/dev/index.tsx @@ -14,11 +14,7 @@ * limitations under the License. */ -export type Component = { - id: string; -}; +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; -export type Inventory = { - list(): Promise; - item(id: string): Promise; -}; +createDevApp().registerPlugin(plugin).render(); diff --git a/plugins/inventory/package.json b/plugins/catalog/package.json similarity index 65% rename from plugins/inventory/package.json rename to plugins/catalog/package.json index ae871eb0bf..1987dea1f5 100644 --- a/plugins/inventory/package.json +++ b/plugins/catalog/package.json @@ -1,8 +1,8 @@ { - "name": "@backstage/plugin-inventory", - "version": "0.1.1-alpha.4", + "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,29 +11,32 @@ "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": { - "@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", "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.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": "^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" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx new file mode 100644 index 0000000000..01d821e175 --- /dev/null +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -0,0 +1,32 @@ +/* + * 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 CatalogPage from './CatalogPage'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; + +describe('CatalogPage', () => { + it('should render', async () => { + const rendered = render( + + + , + ); + 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 new file mode 100644 index 0000000000..9972b9d71f --- /dev/null +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -0,0 +1,42 @@ +/* + * 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 { 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'; + +const componentFactory: ComponentFactory = MockComponentFactory; + +const CatalogPage: FC<{}> = () => { + const { value, error, loading } = useAsync(componentFactory.getAllComponents); + return ( + +
+ + + + + ); +}; + +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/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/inventory-backend/src/inventory/StaticInventory.ts b/plugins/catalog/src/data/mock-factory.ts similarity index 53% rename from plugins/inventory-backend/src/inventory/StaticInventory.ts rename to plugins/catalog/src/data/mock-factory.ts index 4569ae8ed2..010036d732 100644 --- a/plugins/inventory-backend/src/inventory/StaticInventory.ts +++ b/plugins/catalog/src/data/mock-factory.ts @@ -13,22 +13,19 @@ * 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'; -import { NotFoundError } from '@backstage/backend-common'; -import { Component, Inventory } from './types'; - -export class StaticInventory implements Inventory { - constructor(private components: Component[]) {} - - async list(): Promise { - return this.components.slice(); - } - - async item(id: string): Promise { - const item = this.components.find((i) => i.id === id); - if (!item) { - throw new NotFoundError(`Found no component with ID ${id}`); - } - return item; - } -} +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, + ), + ); + }, +}; 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 68% rename from plugins/inventory/tsconfig.json rename to plugins/catalog/tsconfig.json index 5a3931ffce..b663b01fa2 100644 --- a/plugins/inventory/tsconfig.json +++ b/plugins/catalog/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.json", - "include": ["src"], + "include": ["src", "dev"], "compilerOptions": {} } diff --git a/plugins/circleci/.eslintrc.js b/plugins/circleci/.eslintrc.js index 13573efa9c..f1f18a58c8 100644 --- a/plugins/circleci/.eslintrc.js +++ b/plugins/circleci/.eslintrc.js @@ -1,3 +1,6 @@ module.exports = { extends: [require.resolve('@backstage/cli/config/eslint')], + rules: { + 'no-use-before-define': ['error', { variables: false }], + }, }; diff --git a/plugins/circleci/src/state/useBuildWithSteps.ts b/plugins/circleci/src/state/useBuildWithSteps.ts index cb0a01c8c1..2787143658 100644 --- a/plugins/circleci/src/state/useBuildWithSteps.ts +++ b/plugins/circleci/src/state/useBuildWithSteps.ts @@ -21,7 +21,6 @@ import { useSettings } from './useSettings'; import { useAsyncPolling } from './useAsyncPolling'; - const INTERVAL_AMOUNT = 3000; export function useBuildWithSteps(buildId: number) { @@ -30,8 +29,11 @@ export function useBuildWithSteps(buildId: number) { const api = useApi(circleCIApiRef); const errorApi = useApi(errorApiRef); - const {isPolling, startPolling, stopPolling} = useAsyncPolling(() => getBuildWithSteps(), INTERVAL_AMOUNT); - + /* eslint-disable-line no-use-before-define */ + const { isPolling, startPolling, stopPolling } = useAsyncPolling( + () => getBuildWithSteps(), + INTERVAL_AMOUNT, + ); const getBuildWithSteps = async () => { try { diff --git a/plugins/circleci/src/state/useBuilds.tsx b/plugins/circleci/src/state/useBuilds.tsx index 89f85744c8..9104b134cc 100644 --- a/plugins/circleci/src/state/useBuilds.tsx +++ b/plugins/circleci/src/state/useBuilds.tsx @@ -27,7 +27,11 @@ export function useBuilds() { const api = useApi(circleCIApiRef); const errorApi = useApi(errorApiRef); - const {isPolling, startPolling, stopPolling} = useAsyncPolling(() => getBuilds(), INTERVAL_AMOUNT); + // eslint-disable-line no-use-before-define + const { isPolling, startPolling, stopPolling } = useAsyncPolling( + () => getBuilds(), + INTERVAL_AMOUNT, + ); const getBuilds = async () => { if (settings.owner === '' || settings.repo === '') return; @@ -40,10 +44,11 @@ export function useBuilds() { type: GitType.GITHUB, }, }); - if (isPolling) dispatch({ - type: 'setBuilds', - payload: newBuilds, - }); + if (isPolling) + dispatch({ + type: 'setBuilds', + payload: newBuilds, + }); } catch (e) { errorApi.post(e); } @@ -64,8 +69,6 @@ export function useBuilds() { } }; - - return [ builds, { 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..50c7e0be84 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,8 +1,8 @@ { "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", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -10,31 +10,35 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "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", "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.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": "^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" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } 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/graphiql/package.json b/plugins/graphiql/package.json index 3740ac01a2..a90bd27a1f 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" @@ -17,18 +17,20 @@ ], "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": { - "@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", @@ -36,22 +38,22 @@ "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.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", - "@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", - "react-router-dom": "^5.1.2" + "react-router-dom": "^5.2.0" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } 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..2351780f2c --- /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); }, }); diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json index 121bed61da..bd97a6ec4f 100644 --- a/plugins/home-page/package.json +++ b/plugins/home-page/package.json @@ -1,8 +1,8 @@ { "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", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -11,31 +11,33 @@ "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": { - "@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", "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.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": "^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.1.2", - "jest-fetch-mock": "^3.0.3" + "jest-fetch-mock": "^3.0.3", + "react-router-dom": "^5.2.0" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/inventory-backend/src/inventory/AggregatorInventory.ts b/plugins/inventory-backend/src/inventory/AggregatorInventory.ts deleted file mode 100644 index b768b8eb23..0000000000 --- a/plugins/inventory-backend/src/inventory/AggregatorInventory.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { NotFoundError } from '@backstage/backend-common'; -import { Component, Inventory } from './types'; - -export class AggregatorInventory implements Inventory { - inventories: Inventory[] = []; - - async list(): Promise { - const lists = await Promise.all(this.inventories.map((i) => i.list())); - return lists.flat(); - } - - item(id: string): Promise { - return new Promise((resolve, reject) => { - const promises = this.inventories.map((i) => - i.item(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/service/router.ts b/plugins/inventory-backend/src/service/router.ts deleted file mode 100644 index a05fac08be..0000000000 --- a/plugins/inventory-backend/src/service/router.ts +++ /dev/null @@ -1,50 +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 express from 'express'; -import { Logger } from 'winston'; -import Router from 'express-promise-router'; -import { Inventory } from '../inventory'; - -export interface RouterOptions { - inventory: Inventory; - logger: Logger; -} - -export async function createRouter( - options: RouterOptions, -): Promise { - const inventory = options.inventory; - const logger = options.logger.child({ plugin: 'inventory' }); - - const router = Router(); - router - .get('/', async (req, res) => { - const components = await inventory.list(); - res.status(200).send(components); - }) - .get('/:id', async (req, res) => { - const { id } = req.params; - const component = await inventory.item(id); - res.status(200).send(component); - }); - - const app = express(); - app.set('logger', logger); - app.use('/', router); - - return app; -} 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/inventory/src/components/InventoryPage/InventoryPage.tsx b/plugins/inventory/src/components/InventoryPage/InventoryPage.tsx deleted file mode 100644 index 647899eb35..0000000000 --- a/plugins/inventory/src/components/InventoryPage/InventoryPage.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { FC } 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'; - -// TODO(freben): Connect to backend -const STATIC_DATA = [ - { id: 'backstage-frontend', kind: 'website' }, - { id: 'backstage-backend', kind: 'service' }, - { id: 'backstage-microsite', kind: 'website' }, -]; - -const InventoryPage: FC<{}> = () => { - return ( - -
- - All of it - - -
- - - ID - Kind - - - - {STATIC_DATA.map((d) => ( - - {d.id} - {d.kind} - - ))} - -
- - - - - ); -}; - -export default InventoryPage; diff --git a/plugins/lighthouse/dev/index.tsx b/plugins/lighthouse/dev/index.tsx new file mode 100644 index 0000000000..6496fb658a --- /dev/null +++ b/plugins/lighthouse/dev/index.tsx @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; +import { lighthouseApiRef, LighthouseRestApi } from '../src'; + +createDevApp() + .registerPlugin(plugin) + .registerApiFactory({ + implements: lighthouseApiRef, + deps: {}, + factory: () => new LighthouseRestApi('http://localhost:3003'), + }) + .render(); diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 50ad4c758c..89a9d3709f 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,8 +1,8 @@ { "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", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -10,32 +10,36 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "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", "react": "^16.13.1", "react-dom": "^16.13.1", "react-markdown": "^4.3.1", - "react-router-dom": "^5.1.2", - "react-use": "^13.24.0" + "react-router-dom": "^5.2.0", + "react-use": "^14.2.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": "^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" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } 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/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..812a5585d4 --- /dev/null +++ b/plugins/register-component/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/register-component/package.json b/plugins/register-component/package.json new file mode 100644 index 0000000000..8df06e518b --- /dev/null +++ b/plugins/register-component/package.json @@ -0,0 +1,43 @@ +{ + "name": "@backstage/plugin-register-component", + "version": "0.1.1-alpha.5", + "main": "dist/index.esm.js", + "types": "src/index.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", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@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", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-hook-form": "^5.7.2", + "react-use": "^14.2.0" + }, + "devDependencies": { + "@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" + }, + "files": [ + "dist/**/*.{js,d.ts}" + ] +} 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..fe4ba7845a --- /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/inventory/src/components/InventoryPage/InventoryPage.test.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx similarity index 80% rename from plugins/inventory/src/components/InventoryPage/InventoryPage.test.tsx rename to plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx index fe17da308c..ac2645135e 100644 --- a/plugins/inventory/src/components/InventoryPage/InventoryPage.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx @@ -17,18 +17,18 @@ import React from 'react'; import { render } from '@testing-library/react'; import mockFetch from 'jest-fetch-mock'; -import InventoryPage from './InventoryPage'; +import RegisterComponentPage from './RegisterComponentPage'; import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; -describe('InventoryPage', () => { - it('should render', async () => { +describe('RegisterComponentPage', () => { + it('should render', () => { mockFetch.mockResponse(() => new Promise(() => {})); const rendered = render( - + , ); - expect(await rendered.findByText('backstage-backend')).toBeInTheDocument(); + 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/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index a1a80736de..1c596681cc 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,30 +1,33 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.1.1-alpha.4", + "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": { - "@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-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/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/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..3d6be66644 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,8 +1,8 @@ { "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", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -11,29 +11,32 @@ "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": { - "@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", "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.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": "^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" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } 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(); }); 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/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/): diff --git a/plugins/tech-radar/dev/index.tsx b/plugins/tech-radar/dev/index.tsx new file mode 100644 index 0000000000..ac19b63a90 --- /dev/null +++ b/plugins/tech-radar/dev/index.tsx @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; +import { techRadarApiRef, TechRadar } from '../src'; + +createDevApp() + .registerPlugin(plugin) + .registerApiFactory({ + implements: techRadarApiRef, + deps: {}, + factory: () => new TechRadar({ width: 1500, height: 800 }), + }) + .render(); diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index ef1de13671..ecf863a881 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,8 +1,8 @@ { "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", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, "scripts": { @@ -10,12 +10,15 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", - "clean": "backstage-cli clean" + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean", + "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", @@ -24,21 +27,22 @@ "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.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/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" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } 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..91053a31f7 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -1,8 +1,8 @@ { "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", + "types": "src/index.ts", "private": true, "license": "Apache-2.0", "scripts": { @@ -10,30 +10,34 @@ "lint": "backstage-cli lint", "test": "backstage-cli test", "diff": "backstage-cli plugin:diff", - "clean": "backstage-cli clean" + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean", + "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", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-router-dom": "5.1.2", - "react-use": "^13.24.0" + "react-router-dom": "^5.2.0", + "react-use": "^14.2.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": "^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" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } 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": {} } diff --git a/yarn.lock b/yarn.lock index 80ea25a51e..adf2cb670f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -949,10 +949,10 @@ 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== +"@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== dependencies: regenerator-runtime "^0.13.4" @@ -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" @@ -2238,7 +2248,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== @@ -2256,6 +2266,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" @@ -2752,11 +2780,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" @@ -2769,7 +2797,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== @@ -2865,6 +2893,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" @@ -2980,15 +3013,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" @@ -3705,6 +3738,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" @@ -4031,16 +4071,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 +4102,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" "*" @@ -4155,13 +4186,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" + jest-diff "^25.2.1" + pretty-format "^25.2.1" "@types/jest@^24.0.0": version "24.9.1" @@ -4170,10 +4201,10 @@ dependencies: jest-diff "^24.3.0" -"@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" "*" @@ -4184,18 +4215,16 @@ dependencies: "@types/sizzle" "*" -"@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" "*" - "@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" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" @@ -4486,9 +4515,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" "*" @@ -4564,6 +4593,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" @@ -4590,9 +4624,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" "*" @@ -4606,6 +4640,13 @@ resolved "https://registry.npmjs.org/@types/whatwg-streams/-/whatwg-streams-0.0.7.tgz#28bfe73dc850562296367249c4b32a50db81e9d3" integrity sha512-6sDiSEP6DWcY2ZolsJ2s39ZmsoGQ7KVwBDI3sESQsEm9P2dHTcqnDIHRZFRNtLCzWp7hCFGqYbw5GyfpQnJ01A== +"@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" @@ -4625,6 +4666,11 @@ dependencies: "@types/yargs-parser" "*" +"@types/yup@^0.28.2": + 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" resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" @@ -4964,11 +5010,6 @@ "@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" @@ -5362,6 +5403,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" @@ -5396,6 +5442,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" @@ -6013,7 +6064,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= @@ -6202,7 +6253,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 +6544,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" @@ -6740,11 +6804,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" @@ -6950,6 +7019,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" @@ -7066,6 +7142,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" @@ -7129,6 +7210,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" @@ -7173,7 +7259,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== @@ -7235,7 +7321,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== @@ -7262,6 +7348,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" @@ -7895,7 +7993,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== @@ -8175,6 +8273,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" @@ -8227,6 +8332,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" @@ -8341,6 +8451,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" @@ -8394,10 +8509,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" @@ -8971,6 +9086,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" @@ -9235,6 +9355,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" @@ -9893,6 +10018,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" @@ -9920,6 +10061,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" @@ -9956,6 +10102,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" @@ -10325,7 +10478,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== @@ -10337,6 +10490,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" @@ -10489,6 +10647,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" @@ -10652,6 +10817,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" @@ -10821,6 +11003,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" @@ -10915,6 +11102,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" @@ -11121,6 +11313,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" @@ -11242,13 +11439,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" @@ -11262,7 +11459,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== @@ -11345,13 +11542,20 @@ immutable@>=3.8.2, immutable@^3.8.2: resolved "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM= -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" @@ -11443,7 +11647,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== @@ -11638,6 +11842,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" @@ -11890,6 +12102,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" @@ -11910,6 +12130,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" @@ -12023,6 +12248,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" @@ -12091,6 +12323,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" @@ -12126,6 +12365,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" @@ -12405,15 +12649,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" @@ -12535,6 +12779,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" @@ -13176,6 +13425,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" @@ -13352,6 +13606,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" @@ -13400,6 +13661,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" @@ -13422,6 +13704,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" @@ -13650,6 +13939,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" @@ -13866,6 +14169,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" @@ -14092,11 +14400,24 @@ 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" + 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" @@ -14176,6 +14497,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" @@ -14195,7 +14523,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= @@ -14526,6 +14854,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" @@ -14538,14 +14871,13 @@ 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== +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.4.0" - gud "^1.0.0" - tiny-warning "^1.0.2" + "@babel/runtime" "^7.5.5" + tiny-warning "^1.0.3" mini-css-extract-plugin@0.9.0: version "0.9.0" @@ -14707,10 +15039,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" @@ -14855,6 +15187,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" @@ -15016,6 +15357,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" @@ -15024,9 +15381,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" @@ -15037,7 +15394,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" @@ -15096,6 +15453,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" @@ -15162,7 +15524,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== @@ -15342,7 +15704,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== @@ -15435,6 +15797,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" @@ -15463,7 +15835,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= @@ -15668,6 +16048,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" @@ -15771,11 +16156,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" @@ -15783,6 +16163,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" @@ -15808,6 +16196,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" @@ -15835,6 +16230,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" @@ -15931,6 +16336,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" @@ -16069,6 +16483,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" @@ -16137,6 +16563,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" @@ -16157,6 +16588,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" @@ -16505,7 +16941,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== @@ -16663,10 +17099,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" @@ -17016,6 +17452,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" @@ -17049,12 +17490,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" @@ -17163,7 +17604,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== @@ -17172,6 +17613,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" @@ -17283,6 +17729,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" @@ -17298,16 +17751,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" @@ -17415,15 +17863,14 @@ 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.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== @@ -17612,6 +18059,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" @@ -17699,29 +18151,29 @@ 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.1.2, 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, 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== +react-router@5.2.0, react-router@^5.1.2, 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== 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" + mini-create-react-context "^0.4.0" path-to-regexp "^1.7.0" prop-types "^15.6.2" react-is "^16.6.0" @@ -17830,6 +18282,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" @@ -17867,13 +18330,13 @@ react-use@^13.0.0: ts-easing "^0.2.0" tslib "^1.10.0" -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" @@ -18276,6 +18739,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" @@ -18352,14 +18822,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" @@ -18517,13 +18987,27 @@ 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.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== + 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" @@ -18610,7 +19094,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== @@ -18645,24 +19129,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: @@ -18684,7 +19168,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== @@ -18774,10 +19258,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" @@ -18939,7 +19423,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== @@ -19495,6 +19979,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" @@ -20109,6 +20601,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" @@ -20145,7 +20642,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== @@ -20159,17 +20656,22 @@ 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" 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" @@ -20385,6 +20887,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" @@ -20417,7 +20924,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== @@ -20458,6 +20965,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" @@ -20498,6 +21010,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" @@ -20595,10 +21112,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" @@ -20623,9 +21140,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" @@ -20739,11 +21256,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" @@ -20775,6 +21297,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" @@ -20987,6 +21514,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" @@ -21025,6 +21571,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" @@ -21128,6 +21681,16 @@ 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== + +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" @@ -21142,6 +21705,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" @@ -21867,6 +22437,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" @@ -21919,6 +22494,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" @@ -22056,7 +22638,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== @@ -22105,6 +22687,24 @@ 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" + 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"