Merge branch 'master' of github.com:splunk/backstage into feature/update-splunk-on-call-invalid-team
@@ -0,0 +1,82 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Cache management has been added to the Backstage backend.
|
||||
|
||||
To apply this change to an existing app, make the following changes:
|
||||
|
||||
```diff
|
||||
// packages/backend/src/types.ts
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
+ PluginCacheManager,
|
||||
PluginDatabaseManager,
|
||||
PluginEndpointDiscovery,
|
||||
UrlReader,
|
||||
} from '@backstage/backend-common';
|
||||
|
||||
export type PluginEnvironment = {
|
||||
logger: Logger;
|
||||
database: PluginDatabaseManager;
|
||||
+ cache: PluginCacheManager;
|
||||
config: Config;
|
||||
reader: UrlReader
|
||||
discovery: PluginEndpointDiscovery;
|
||||
};
|
||||
```
|
||||
|
||||
```diff
|
||||
// packages/backend/src/index.ts
|
||||
|
||||
import Router from 'express-promise-router';
|
||||
import {
|
||||
createServiceBuilder,
|
||||
loadBackendConfig,
|
||||
getRootLogger,
|
||||
useHotMemoize,
|
||||
notFoundHandler,
|
||||
+ CacheManager,
|
||||
SingleConnectionDatabaseManager,
|
||||
SingleHostDiscovery,
|
||||
UrlReaders,
|
||||
} from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
function makeCreateEnv(config: Config) {
|
||||
const root = getRootLogger();
|
||||
const reader = UrlReaders.default({ logger: root, config });
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
|
||||
root.info(`Created UrlReader ${reader}`);
|
||||
|
||||
const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
|
||||
+ const cacheManager = CacheManager.fromConfig(config);
|
||||
|
||||
return (plugin: string): PluginEnvironment => {
|
||||
const logger = root.child({ type: 'plugin', plugin });
|
||||
const database = databaseManager.forPlugin(plugin);
|
||||
- return { logger, database, config, reader, discovery };
|
||||
+ const cache = cacheManager.forPlugin(plugin);
|
||||
+ return { logger, database, cache, config, reader, discovery };
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
To configure a cache store, add a `backend.cache` key to your app-config.yaml.
|
||||
|
||||
```diff
|
||||
// app-config.yaml
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7000
|
||||
listen:
|
||||
port: 7000
|
||||
database:
|
||||
client: sqlite3
|
||||
connection: ':memory:'
|
||||
+ cache:
|
||||
+ store: memory
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/config-loader': patch
|
||||
---
|
||||
|
||||
Resolve the path to app-config.yaml from the current working directory. This will allow use of `yarn link` or running the CLI in other directories and improve the experience for local backstage development.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
SignInPage: Show login page while pop-up is being displayed when `auto` prop is set.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Bumped `eslint-config-prettier` to `8.x`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-jenkins': patch
|
||||
---
|
||||
|
||||
Show error in Jenkins card for errors exposed by the Jenkins API
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Add support for fullHeight variant to the AboutCard
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Bumped the `@gitbeaker` dependencies to `29.x`.
|
||||
|
||||
To apply this change to an existing app, update all `@gitbeaker/*` dependencies in your `package.json`s to point to `^29.2.0`. Then run `yarn install` at the root of your project.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Bump `@gitbeaker` dependencies to `29.x`.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/catalog-model': minor
|
||||
---
|
||||
|
||||
Breaking changes:
|
||||
|
||||
- The long-deprecated `schemaValidator` export is finally removed.
|
||||
|
||||
Additions:
|
||||
|
||||
- The `EntityEnvelope` type, which is a supertype of `Entity`.
|
||||
- The `entityEnvelopeSchemaValidator` function, which returns a validator for an `EntityEnvelope` or its subtypes, based on a JSON schema.
|
||||
- The `entitySchemaValidator` function, which returns a validator for an `Entity` or its subtypes, based on a JSON schema.
|
||||
- The `entityKindSchemaValidator` function, which returns a specialized validator for custom `Entity` kinds, based on a JSON schema.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Bump webpack-node-externals from `2.5.2` to `3.0.0`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-github-deployments': patch
|
||||
---
|
||||
|
||||
Handle deployments without a commit
|
||||
@@ -1,133 +1,58 @@
|
||||
Apdex
|
||||
Api
|
||||
automations
|
||||
Autoscaling
|
||||
Avro
|
||||
Bigtable
|
||||
Billett
|
||||
Bitrise
|
||||
Blackbox
|
||||
Chai
|
||||
Changesets
|
||||
Chanwit
|
||||
Cloudformation
|
||||
Cobertura
|
||||
Codecov
|
||||
Codehilite
|
||||
Config
|
||||
Datadog
|
||||
Debounce
|
||||
Discoverability
|
||||
Dockerfile
|
||||
dockerfiles
|
||||
Dockerize
|
||||
Docusaurus
|
||||
Env
|
||||
Expedia
|
||||
Fargate
|
||||
Figma
|
||||
Firekube
|
||||
Fiverr
|
||||
GitHub
|
||||
GitLab
|
||||
Grafana
|
||||
GraphQL
|
||||
Hackathons
|
||||
Helidon
|
||||
Heroku
|
||||
hoc
|
||||
Hostname
|
||||
Iain
|
||||
JaCoCo
|
||||
JavaScript
|
||||
Jira
|
||||
Kaewkasi
|
||||
kubernetes
|
||||
Knex
|
||||
Leasot
|
||||
Lerna
|
||||
LocalStack
|
||||
Luxon
|
||||
Minikube
|
||||
Minio
|
||||
Mkdocs
|
||||
Monorepo
|
||||
microservices
|
||||
microservice
|
||||
Namespaces
|
||||
OAuth
|
||||
Okta
|
||||
Onboarding
|
||||
Platformize
|
||||
Podman
|
||||
Preprarer
|
||||
Protobuf
|
||||
Proxying
|
||||
Readme
|
||||
readonly
|
||||
rebase
|
||||
Recharts
|
||||
Redash
|
||||
Repo
|
||||
Rollbar
|
||||
Rollup
|
||||
Routable
|
||||
Scaffolder
|
||||
Serverless
|
||||
Sinon
|
||||
Snyk
|
||||
Splunk
|
||||
Spotifiers
|
||||
Spotify
|
||||
Superfences
|
||||
Talkdesk
|
||||
Telenor
|
||||
Templater
|
||||
Templaters
|
||||
todo
|
||||
Tolerations
|
||||
WWW
|
||||
Weaveworks
|
||||
Webpack
|
||||
XML
|
||||
Zalando
|
||||
Zhou
|
||||
abc
|
||||
accessors
|
||||
Apdex
|
||||
api
|
||||
Api
|
||||
apis
|
||||
args
|
||||
asciidoc
|
||||
async
|
||||
automations
|
||||
autoscaling
|
||||
Autoscaling
|
||||
Avro
|
||||
backrub
|
||||
Bigtable
|
||||
Billett
|
||||
Bitrise
|
||||
Blackbox
|
||||
bool
|
||||
boolean
|
||||
builtins
|
||||
Chai
|
||||
changeset
|
||||
changesets
|
||||
Changesets
|
||||
chanwit
|
||||
Chanwit
|
||||
ci
|
||||
cisphobia
|
||||
cissexist
|
||||
classname
|
||||
cli
|
||||
cloudbuild
|
||||
Cloudformation
|
||||
cncf
|
||||
Cobertura
|
||||
codeblocks
|
||||
Codecov
|
||||
codehilite
|
||||
Codehilite
|
||||
codeowners
|
||||
composability
|
||||
composable
|
||||
config
|
||||
Config
|
||||
configmaps
|
||||
configs
|
||||
const
|
||||
cookiecutter
|
||||
css
|
||||
Datadog
|
||||
dataflow
|
||||
deadnaming
|
||||
debounce
|
||||
Debounce
|
||||
declaratively
|
||||
deps
|
||||
destructured
|
||||
@@ -135,60 +60,99 @@ dev
|
||||
devops
|
||||
devs
|
||||
discoverability
|
||||
Discoverability
|
||||
dls
|
||||
docgen
|
||||
Dockerfile
|
||||
dockerfiles
|
||||
Dockerize
|
||||
dockerode
|
||||
Docusaurus
|
||||
env
|
||||
Env
|
||||
esbuild
|
||||
eslint
|
||||
etag
|
||||
Expedia
|
||||
facto
|
||||
failover
|
||||
Fargate
|
||||
Figma
|
||||
Firekube
|
||||
Fiverr
|
||||
gitbeaker
|
||||
GitHub
|
||||
GitLab
|
||||
Grafana
|
||||
graphql
|
||||
GraphQL
|
||||
graphviz
|
||||
Hackathons
|
||||
haproxy
|
||||
Helidon
|
||||
Heroku
|
||||
hoc
|
||||
horizontalpodautoscalers
|
||||
Hostname
|
||||
hotspots
|
||||
html
|
||||
http
|
||||
https
|
||||
Iain
|
||||
img
|
||||
incentivised
|
||||
inlined
|
||||
inlinehilite
|
||||
interop
|
||||
JaCoCo
|
||||
JavaScript
|
||||
Jira
|
||||
jq
|
||||
js
|
||||
json
|
||||
jsonnet
|
||||
jsx
|
||||
Kaewkasi
|
||||
Keyv
|
||||
Knex
|
||||
kubectl
|
||||
kubernetes
|
||||
kubernetes
|
||||
learnings
|
||||
Leasot
|
||||
lerna
|
||||
Lerna
|
||||
LocalStack
|
||||
lockfile
|
||||
lunr
|
||||
Luxon
|
||||
magiclink
|
||||
mailto
|
||||
maintainership
|
||||
makefile
|
||||
md
|
||||
memcache
|
||||
microservice
|
||||
microservices
|
||||
microsite
|
||||
middleware
|
||||
minikube
|
||||
Minikube
|
||||
Minio
|
||||
misconfiguration
|
||||
misconfigured
|
||||
misgendering
|
||||
mkdocs
|
||||
Mkdocs
|
||||
monorepo
|
||||
Monorepo
|
||||
monorepos
|
||||
msw
|
||||
mysql
|
||||
namespace
|
||||
namespaced
|
||||
namespaces
|
||||
Namespaces
|
||||
namespacing
|
||||
neuro
|
||||
newrelic
|
||||
@@ -200,43 +164,67 @@ noop
|
||||
npm
|
||||
nvarchar
|
||||
nvm
|
||||
OAuth
|
||||
octokit
|
||||
oidc
|
||||
Okta
|
||||
onboarding
|
||||
Onboarding
|
||||
pagerduty
|
||||
pageview
|
||||
parallelization
|
||||
plantuml
|
||||
Platformize
|
||||
Podman
|
||||
postgres
|
||||
postpack
|
||||
pre
|
||||
prebaked
|
||||
preconfigured
|
||||
prepack
|
||||
Preprarer
|
||||
productional
|
||||
Protobuf
|
||||
proxying
|
||||
Proxying
|
||||
pygments
|
||||
pymdownx
|
||||
rankdir
|
||||
readme
|
||||
Readme
|
||||
readonly
|
||||
rebase
|
||||
Recharts
|
||||
Redash
|
||||
replicasets
|
||||
repo
|
||||
Repo
|
||||
repos
|
||||
rerender
|
||||
rollbar
|
||||
Rollbar
|
||||
Rollup
|
||||
routable
|
||||
Routable
|
||||
rst
|
||||
rsync
|
||||
ruleset
|
||||
sam
|
||||
scaffolded
|
||||
scaffolder
|
||||
Scaffolder
|
||||
seb
|
||||
semlas
|
||||
semver
|
||||
Serverless
|
||||
Sinon
|
||||
Snyk
|
||||
sourcemaps
|
||||
sparklines
|
||||
Splunk
|
||||
Spotifiers
|
||||
spotify
|
||||
Spotify
|
||||
sqlite
|
||||
squidfunk
|
||||
src
|
||||
@@ -247,17 +235,25 @@ subkey
|
||||
subroutes
|
||||
subtree
|
||||
superfences
|
||||
Superfences
|
||||
superset
|
||||
supertype
|
||||
talkdesk
|
||||
Talkdesk
|
||||
tasklist
|
||||
techdocs
|
||||
Telenor
|
||||
templated
|
||||
templater
|
||||
Templater
|
||||
templaters
|
||||
Templaters
|
||||
theia
|
||||
thumbsup
|
||||
toc
|
||||
todo
|
||||
tolerations
|
||||
Tolerations
|
||||
toolchain
|
||||
toolsets
|
||||
tooltip
|
||||
@@ -273,11 +269,18 @@ untracked
|
||||
upvote
|
||||
url
|
||||
utils
|
||||
validator
|
||||
validators
|
||||
varchar
|
||||
Weaveworks
|
||||
Webpack
|
||||
winston
|
||||
www
|
||||
WWW
|
||||
XML
|
||||
xyz
|
||||
yaml
|
||||
Zalando
|
||||
Zhou
|
||||
zoomable
|
||||
zsh
|
||||
|
||||
@@ -29,6 +29,8 @@ backend:
|
||||
database:
|
||||
client: sqlite3
|
||||
connection: ':memory:'
|
||||
cache:
|
||||
store: memory
|
||||
cors:
|
||||
origin: http://localhost:3000
|
||||
methods: [GET, POST, PUT, DELETE]
|
||||
|
||||
@@ -167,4 +167,4 @@ Once it's deployed, after going through the AAD authentication flow, Backstage s
|
||||
|
||||
<!-- links -->
|
||||
|
||||
[monorepo-app-setup-with-auth-ms]: https://backstage.io/docs/tutorials/quickstart-app-auth#the-auth-configuration
|
||||
[monorepo-app-setup-with-auth-ms]: https://backstage.io/docs/auth/microsoft/provider
|
||||
|
||||
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@@ -222,17 +222,17 @@ You can see it in the above full example which is a separate step and it looks a
|
||||
little like this:
|
||||
|
||||
```yaml
|
||||
- title: Choose a location
|
||||
required:
|
||||
- repoUrl
|
||||
properties:
|
||||
repoUrl:
|
||||
title: Repository Location
|
||||
type: string
|
||||
ui:field: RepoUrlPicker
|
||||
ui:options:
|
||||
allowedHosts:
|
||||
- github.com
|
||||
- title: Choose a location
|
||||
required:
|
||||
- repoUrl
|
||||
properties:
|
||||
repoUrl:
|
||||
title: Repository Location
|
||||
type: string
|
||||
ui:field: RepoUrlPicker
|
||||
ui:options:
|
||||
allowedHosts:
|
||||
- github.com
|
||||
```
|
||||
|
||||
The `allowedHosts` part should be set to where you wish to enable this template
|
||||
|
||||
@@ -57,11 +57,11 @@ If Backstage is configured to use GitHub Apps authentication you must grant
|
||||
`Read-Only` access for `Members` under `Organization` in order to ingest users
|
||||
correctly. You can modify the app's permissions under the organization settings,
|
||||
`https://github.com/organizations/{ORG}/settings/apps/{APP_NAME}/permissions`.
|
||||

|
||||

|
||||
|
||||
**Please note that when you change permissions, the app owner will get an email
|
||||
that must be approved first before the changes are applied.**
|
||||

|
||||

|
||||
|
||||
Locations point out the specific org(s) you want to import. The `type` of these
|
||||
locations must be `github-org`, and the `target` must point to the exact URL of
|
||||
|
||||
@@ -182,6 +182,21 @@ work but
|
||||
[aren't tested as fully](https://github.com/backstage/backstage/issues/2460)
|
||||
yet.
|
||||
|
||||
## Cache
|
||||
|
||||
The Backstage backend and its builtin plugins are also able to leverage cache
|
||||
stores as a means of improving performance or reliability. Similar to how
|
||||
databases are supported, plugins receive logically separated cache connections,
|
||||
which are powered by [Keyv](https://github.com/lukechilds/keyv) under the hood.
|
||||
|
||||
At this time of writing, Backstage can be configured to use one of two cache
|
||||
stores: memory, which is mainly used for local testing, and memcache, which is a
|
||||
cache store better suited for production deployment. The right cache store for
|
||||
your Backstage instance will depend on your own run-time constraints and those
|
||||
required of the plugins you're running.
|
||||
|
||||
Contributions supporting other cache stores are welcome!
|
||||
|
||||
## Containerization
|
||||
|
||||
The example Backstage architecture shown above would Dockerize into three
|
||||
|
||||
@@ -1,371 +0,0 @@
|
||||
---
|
||||
id: quickstart-app-auth
|
||||
title: Monorepo App Setup With Authentication
|
||||
---
|
||||
|
||||
###### January 8th 2021 - @backstage/create-app - v0.4.5
|
||||
|
||||
<br />
|
||||
|
||||
> This document takes you through setting up a Backstage app that runs in your
|
||||
> own environment. It starts with a skeleton install and verifying of the
|
||||
> monorepo's functionality. Next, authentication is added and tested.
|
||||
>
|
||||
> This document assumes you have Node.js 12 or 14 active along with Yarn and
|
||||
> Python. Please note, that at the time of this writing, the current version is
|
||||
> v0.4.5. This guide can still be used with future versions, just, verify as you
|
||||
> go.
|
||||
|
||||
# The Skeleton Application
|
||||
|
||||
From the terminal:
|
||||
|
||||
1. Create a (monorepo) application: `npx @backstage/create-app`
|
||||
1. Enter an `id` for your new app like `mybiz-backstage` I went with
|
||||
`simple-backstage-app`
|
||||
1. Choose `SQLite` as your database. This is the quickest way to get started as
|
||||
PostgreSQL requires additional setup not covered here.
|
||||
1. Start your backend: `yarn --cwd packages/backend start`
|
||||
|
||||
```zsh
|
||||
# You should see positive verbiage in your terminal output
|
||||
2020-09-11T22:20:26.712Z backstage info Listening on :7000
|
||||
```
|
||||
|
||||
5. Finally, start the frontend. Open a new terminal window and from the root of
|
||||
your project, run: `yarn start`
|
||||
|
||||
```zsh
|
||||
# You should see positive verbiage in your terminal output
|
||||
ℹ 「wds」: Project is running at http://localhost:3000/
|
||||
```
|
||||
|
||||
Once the app compiles, a browser window should have popped with your stand-alone
|
||||
application loaded at `localhost:3000`. This could take a couple minutes.
|
||||
|
||||
```zsh
|
||||
# You should see positive verbiage in your terminal output
|
||||
ℹℹ 「wdm」: Compiled successfully.
|
||||
```
|
||||
|
||||
Since there is no auth currently configured, you are automatically entered as a
|
||||
guest. Let's fix that now and add auth.
|
||||
|
||||
# The Auth Configuration
|
||||
|
||||
A default Backstage installation includes multiple authentication providers out
|
||||
of the box. The steps to enable new authentication providers in Backstage are
|
||||
very similar to each other, the biggest difference is usually configuring the
|
||||
external authentication provider. Please see a subset of possible providers and
|
||||
instructions to integrate them below. Steps 1 & 2 are described separately for
|
||||
each provider and steps beyond that are common for all.
|
||||
|
||||
<details><summary>GitHub</summary>
|
||||
<p>
|
||||
|
||||
### 1. Open `app-config.yaml` and change it as follows
|
||||
|
||||
_from:_
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers: {}
|
||||
```
|
||||
|
||||
_to:_
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers:
|
||||
github:
|
||||
development:
|
||||
clientId: ${AUTH_GITHUB_CLIENT_ID}
|
||||
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
|
||||
## uncomment the following line if using enterprise
|
||||
# enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL}
|
||||
```
|
||||
|
||||
### 2. Generate a GitHub client ID and secret
|
||||
|
||||
- Log into http://github.com
|
||||
- Navigate to (Settings > Developer Settings > OAuth Apps > New OAuth
|
||||
App)[https://github.com/settings/applications/new]
|
||||
- Set Homepage URL = `http://localhost:3000`
|
||||
- Set Callback URL = `http://localhost:7000/api/auth/github`
|
||||
- Click [Register application]
|
||||
- On the next page, copy and paste your new Client ID and Client Secret to
|
||||
environment variables defined in the `app-config.yaml` file,
|
||||
`AUTH_GITHUB_CLIENT_ID` & `AUTH_GITHUB_CLIENT_SECRET`
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details><summary>GitLab</summary>
|
||||
<p>
|
||||
|
||||
### 1. Open `app-config.yaml` and change it as follows
|
||||
|
||||
_from:_
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers: {}
|
||||
```
|
||||
|
||||
_to:_
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers:
|
||||
gitlab:
|
||||
development:
|
||||
clientId: ${AUTH_GITLAB_CLIENT_ID}
|
||||
clientSecret: ${AUTH_GITLAB_CLIENT_SECRET}
|
||||
audience: https://gitlab.com # Or your self-hosted GitLab instance URL
|
||||
```
|
||||
|
||||
### 2. Generate a GitLab Application client ID and secret
|
||||
|
||||
- Log into GitLab
|
||||
- Navigate to (Profile > Settings >
|
||||
Applications)[https://gitlab.com/-/profile/applications]
|
||||
- Name your application
|
||||
- Set Callback URL = `http://localhost:7000/api/auth/gitlab/handler/frame`
|
||||
- Select the following values:
|
||||
- `read_user` (Read the authenticated user's personal information)
|
||||
- `read_repository` (Allows read-only access to the repository)
|
||||
- `write_repository` (Allows read-write access to the repository)
|
||||
- `openid` (Authenticate using OpenID Connect)
|
||||
- `profile` (Allows read-only access to the user's personal information using
|
||||
OpenID Connect)
|
||||
- `email` (Allows read-only access to the user's primary email address using
|
||||
OpenID Connect)
|
||||
- Click [Save application]
|
||||
- On the next page, copy and paste your new Application ID and Secret to
|
||||
environment variables defined in the `app-config.yaml` file,
|
||||
`AUTH_GITLAB_CLIENT_ID` & `AUTH_GITLAB_CLIENT_SECRET`
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details><summary>Google</summary>
|
||||
<p>
|
||||
|
||||
### 1. Open `app-config.yaml` and change it as follows
|
||||
|
||||
_from:_
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers: {}
|
||||
```
|
||||
|
||||
_to:_
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers:
|
||||
google:
|
||||
development:
|
||||
clientId: ${AUTH_GOOGLE_CLIENT_ID}
|
||||
clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET}
|
||||
```
|
||||
|
||||
### 2. Generate Google Credentials in Google Cloud console
|
||||
|
||||
- Log into https://console.cloud.google.com
|
||||
- Select or create a new project from the dropdown on the top bar
|
||||
- Navigate to (APIs & Services >
|
||||
Credentials)[https://console.cloud.google.com/apis/credentials]
|
||||
- Click Create Credentials and select [OAuth client ID]
|
||||
- Select Web Application as the application type
|
||||
- Add new Authorised JavaScript origin = `http://localhost:3000`
|
||||
- Add new Authorised redirect URI =
|
||||
`http://localhost:7000/api/auth/google/handler/frame`
|
||||
- Click [Save application]
|
||||
- Google should display a modal with your Client ID and Secret. Copy and paste
|
||||
those to environment variables defined in the `app-config.yaml` file,
|
||||
`AUTH_GOOGLE_CLIENT_ID` & `AUTH_GOOGLE_CLIENT_SECRET`
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details><summary>Microsoft</summary>
|
||||
<p>
|
||||
|
||||
### 1. Open `app-config.yaml` and change it as follows
|
||||
|
||||
_from:_
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers: {}
|
||||
```
|
||||
|
||||
_to:_
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers:
|
||||
microsoft:
|
||||
development:
|
||||
clientId: ${AUTH_MICROSOFT_CLIENT_ID}
|
||||
clientSecret: ${AUTH_MICROSOFT_CLIENT_SECRET}
|
||||
tenantId: ${AUTH_MICROSOFT_TENANT_ID}
|
||||
```
|
||||
|
||||
### 2. Create a Microsoft App Registration in Microsoft Portal
|
||||
|
||||
- Log into https://portal.azure.com
|
||||
- Navigate to (Azure Active Directory > App
|
||||
Registrations)[https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps]
|
||||
- Create a New Registration
|
||||
- Add new Redirect URI = `http://localhost:3000`
|
||||
- Add new Authorised redirect URI =
|
||||
`http://localhost:7000/api/auth/microsoft/handler/frame`
|
||||
- Click [Save application]
|
||||
- Set environment variable `AUTH_MICROSOFT_CLIENT_ID` from
|
||||
`Application (client) Id` displayed on the directory page
|
||||
- Set environment variable `AUTH_MICROSOFT_TENANT_ID` from
|
||||
`Directory (tenant) ID` displayed on the directory page
|
||||
- Navigate to Certificates & Secrets section and click [Create a new secret]
|
||||
- Set environment variable `AUTH_MICROSOFT_CLIENT_SECRET` from the `value` field
|
||||
created.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details><summary>Auth0</summary>
|
||||
<p>
|
||||
|
||||
### 1. Open `app-config.yaml` and change it as follows
|
||||
|
||||
_from:_
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers: {}
|
||||
```
|
||||
|
||||
_to:_
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
providers:
|
||||
auth0:
|
||||
development:
|
||||
clientId: ${AUTH_AUTH0_CLIENT_ID}
|
||||
clientSecret: ${AUTH_AUTH0_CLIENT_SECRET}
|
||||
domain: ${AUTH_AUTH0_DOMAIN_ID}
|
||||
```
|
||||
|
||||
### 2. Create an Auth0 application in the Auth0 management console
|
||||
|
||||
- Log into https://manage.auth0.com/dashboard/
|
||||
- Navigate to Applications
|
||||
- Create a New Application
|
||||
- Select Single Page Web Application
|
||||
- Go to Settings tab
|
||||
- Add new line to Allowed Callback URLs =
|
||||
`http://localhost:7000/api/auth/auth0/handler/frame`
|
||||
- Click [Save Changes]
|
||||
- Set environment variables displayed on the Basic Information page
|
||||
- `AUTH_AUTH0_CLIENT_ID` from `Client ID` displayed on Auth0 application page
|
||||
- `AUTH_AUTH0_CLIENT_SECRET` from `Client Secret` displayed on Auth0
|
||||
application page
|
||||
- `AUTH_AUTH0_DOMAIN_ID` from `Domain` displayed on Auth0 application page
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
### 3. Set environment variables in whatever fashion is easiest for you. I chose to
|
||||
|
||||
add mine to my `.zshrc` profile.
|
||||
|
||||
```zsh
|
||||
# For macOS Catalina & Z Shell
|
||||
# ------ simple-backstage-app GitHub
|
||||
#
|
||||
# (Change the name of the environment variables based on your auth setup above)
|
||||
export AUTH_GITHUB_CLIENT_ID=xxx
|
||||
export AUTH_GITHUB_CLIENT_SECRET=xxx
|
||||
# export AUTH_GITHUB_ENTERPRISE_INSTANCE_URL=https://github.{MY_BIZ}.com
|
||||
```
|
||||
|
||||
### 4. And of course I need to source that file.
|
||||
|
||||
```zsh
|
||||
# Loading the new variables
|
||||
% source ~/.zshrc
|
||||
|
||||
# Any other currently opened terminals need to be restarted to pick up the new values
|
||||
# verify your setup by running env
|
||||
% env
|
||||
# should output something like
|
||||
> ...
|
||||
> AUTH_GITHUB_CLIENT_ID=xxx
|
||||
> AUTH_GITHUB_CLIENT_SECRET=xxx
|
||||
> ...
|
||||
```
|
||||
|
||||
### 5. Open and change _root > packages > app > src >_ `App.tsx` to use correct
|
||||
|
||||
authentication provider reference
|
||||
|
||||
```tsx
|
||||
import { githubAuthApiRef, SignInPage } from '@backstage/core';
|
||||
```
|
||||
|
||||
Modify the imported reference based on the authentication method you selected
|
||||
above:
|
||||
|
||||
| Auth Provider | Import Name |
|
||||
| ------------- | ------------------- |
|
||||
| GitHub | githubAuthApiRef |
|
||||
| GitLab | gitlabAuthApiRef |
|
||||
| Google | googleAuthApiRef |
|
||||
| Microsoft | microsoftAuthApiRef |
|
||||
| Auth0 | auth0AuthApiRef |
|
||||
|
||||
### 6. In the same file, modify createApp
|
||||
|
||||
Remember to add or modify a `SignInPage` component for `createApp`, using
|
||||
provider information based on the table above.
|
||||
|
||||
```tsx
|
||||
const app = createApp({
|
||||
apis,
|
||||
components: {
|
||||
SignInPage: props => (
|
||||
<SignInPage
|
||||
{...props}
|
||||
auto
|
||||
provider={{
|
||||
id: 'github-auth-provider',
|
||||
title: 'GitHub',
|
||||
message: 'Simple Backstage Application Login',
|
||||
apiRef: githubAuthApiRef,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
After finishing setting up one (or multiple) authentication providers defined
|
||||
above you can start the backend and frontend as before
|
||||
|
||||
When the browser loads, you should be presented with a login page for GitHub.
|
||||
Login as usual with your GitHub account. If this is your first time, you will be
|
||||
asked to authorize and then are redirected to the catalog page if all is well.
|
||||
|
||||
For more information you can clone
|
||||
[the backstage-auth-example repository](https://github.com/RoadieHQ/backstage-auth-example).
|
||||
Each authentication setting is set up there on a branch named after the
|
||||
authentication provider.
|
||||
|
||||
# Where to go from here
|
||||
|
||||
> You're probably eager to write your first custom plugin. Follow this next
|
||||
> tutorial for an in-depth look at a custom GitHub repository browser plugin.
|
||||
> [Adding Custom Plugin to Existing Monorepo App](quickstart-app-plugin.md).
|
||||
@@ -35,8 +35,8 @@ title: Adding Custom Plugin to Existing Monorepo App
|
||||
1. When the process finishes, let's start the backend:
|
||||
`yarn --cwd packages/backend start`
|
||||
1. If you see errors starting, refer to
|
||||
[Auth Configuration](https://backstage.io/docs/tutorials/quickstart-app-auth#the-auth-configuration)
|
||||
for more information on environment variables.
|
||||
[Auth Configuration](https://backstage.io/docs/auth/) for more information on
|
||||
environment variables.
|
||||
1. And now the frontend, from a new terminal window and the root of your
|
||||
project: `yarn start`
|
||||
1. As usual, a browser window should popup loading the App.
|
||||
|
||||
@@ -72,7 +72,7 @@ Within today’s complex development environments, there are barriers both big a
|
||||
|
||||
After talking with companies who have already adopted Backstage, we’ve seen a few common strategies for getting started. The different strategies are based on the size of your engineering org (which often also corresponds with how fast you’re growing).
|
||||
|
||||

|
||||

|
||||
<br>
|
||||
|
||||
### My org has ~200 engineers (and is growing fast)
|
||||
@@ -93,7 +93,7 @@ You’re big enough to start feeling the pain, and only getting bigger. Onboardi
|
||||
- At this size, you might not have a dedicated platform/infra team in place yet, but Backstage can provide the framework for centralizing and sharing knowledge — from managing compliance requirements to finding the right API documentation.
|
||||
- This allows both new and old engineers to collaborate more efficiently, easily discover best practices, and cuts down on duplicated work (e.g., a new team doesn’t end up rebuilding a database that already exists but nobody can find).
|
||||
|
||||

|
||||

|
||||
|
||||
### My org has ~1,000 engineers
|
||||
|
||||
@@ -112,7 +112,7 @@ You’re officially big. Lots and lots of teams are managing lots and lots of so
|
||||
- The service catalog also helps your developers visualize your existing software ecosystem, beyond the software they own. And with Backstage Software Templates, every new software component is already added to the catalog.
|
||||
- Even at this scale, a small platform team should be all it takes to build and maintain your own version of Backstage. At Spotify, we have a 4-person team responsible for our internal version of Backstage, which is used by all of Spotify’s 1,600+ developers.
|
||||
|
||||

|
||||

|
||||
|
||||
### My org has 1,000+ engineers
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 34 KiB |
@@ -239,7 +239,6 @@
|
||||
],
|
||||
"Tutorials": [
|
||||
"tutorials/journey",
|
||||
"tutorials/quickstart-app-auth",
|
||||
"tutorials/quickstart-app-plugin",
|
||||
"tutorials/switching-sqlite-postgres"
|
||||
],
|
||||
|
||||
@@ -154,7 +154,6 @@ nav:
|
||||
- Backend: 'api/backend.md'
|
||||
- Tutorials:
|
||||
- Future developer journey: 'tutorials/journey.md'
|
||||
- Monorepo App Setup With Authentication: 'tutorials/quickstart-app-auth.md'
|
||||
- Adding Custom Plugin to Existing Monorepo App: 'tutorials/quickstart-app-plugin.md'
|
||||
- Switching Backstage from SQLite to PostgreSQL: 'tutorials/switching-sqlite-postgres.md'
|
||||
- Architecture Decision Records (ADRs):
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"@backstage/plugin-search-backend-node": "^0.1.3",
|
||||
"@backstage/plugin-techdocs-backend": "^0.8.1",
|
||||
"@backstage/plugin-todo-backend": "^0.1.5",
|
||||
"@gitbeaker/node": "^28.0.2",
|
||||
"@gitbeaker/node": "^29.2.0",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"azure-devops-node-api": "^10.1.1",
|
||||
"dockerode": "^3.2.1",
|
||||
|
||||
@@ -18,9 +18,9 @@ export const analyzeLocationSchema: yup.ObjectSchema<{
|
||||
// @public (undocumented)
|
||||
interface ApiEntityV1alpha1 extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
// (undocumented)
|
||||
kind: typeof KIND;
|
||||
kind: 'API';
|
||||
// (undocumented)
|
||||
spec: {
|
||||
type: string;
|
||||
@@ -54,9 +54,9 @@ export function compareEntityToRef(entity: Entity, ref: EntityRef | EntityName,
|
||||
// @public (undocumented)
|
||||
interface ComponentEntityV1alpha1 extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: typeof API_VERSION_2[number];
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
// (undocumented)
|
||||
kind: typeof KIND_2;
|
||||
kind: 'Component';
|
||||
// (undocumented)
|
||||
spec: {
|
||||
type: string;
|
||||
@@ -87,9 +87,9 @@ export class DefaultNamespaceEntityPolicy implements EntityPolicy {
|
||||
// @public (undocumented)
|
||||
interface DomainEntityV1alpha1 extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: typeof API_VERSION_3[number];
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
// (undocumented)
|
||||
kind: typeof KIND_3;
|
||||
kind: 'Domain';
|
||||
// (undocumented)
|
||||
spec: {
|
||||
owner: string;
|
||||
@@ -107,7 +107,7 @@ export const domainEntityV1alpha1Validator: KindValidator;
|
||||
export const EDIT_URL_ANNOTATION = "backstage.io/edit-url";
|
||||
|
||||
// @public
|
||||
export type Entity = {
|
||||
export type Entity = EntityEnvelope & {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
metadata: EntityMeta;
|
||||
@@ -122,9 +122,25 @@ export const ENTITY_DEFAULT_NAMESPACE = "default";
|
||||
// @public
|
||||
export const ENTITY_META_GENERATED_FIELDS: readonly ["uid", "etag", "generation"];
|
||||
|
||||
// @public
|
||||
export type EntityEnvelope = {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
metadata: {
|
||||
name: string;
|
||||
namespace?: string;
|
||||
};
|
||||
};
|
||||
|
||||
// @public
|
||||
export function entityEnvelopeSchemaValidator<T extends EntityEnvelope = EntityEnvelope>(schema?: unknown): (data: unknown) => T;
|
||||
|
||||
// @public
|
||||
export function entityHasChanges(previous: Entity, next: Entity): boolean;
|
||||
|
||||
// @public
|
||||
export function entityKindSchemaValidator<T extends Entity>(schema: unknown): (data: unknown) => T | false;
|
||||
|
||||
// @public
|
||||
export type EntityLink = {
|
||||
url: string;
|
||||
@@ -184,6 +200,9 @@ export type EntityRelationSpec = {
|
||||
target: EntityName;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function entitySchemaValidator<T extends Entity = Entity>(schema?: unknown): (data: unknown) => T;
|
||||
|
||||
// @public
|
||||
export class FieldFormatEntityPolicy implements EntityPolicy {
|
||||
constructor(validators?: Validators);
|
||||
@@ -212,9 +231,9 @@ export function getEntitySourceLocation(entity: Entity): {
|
||||
// @public (undocumented)
|
||||
interface GroupEntityV1alpha1 extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: typeof API_VERSION_4[number];
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
// (undocumented)
|
||||
kind: typeof KIND_4;
|
||||
kind: 'Group';
|
||||
// (undocumented)
|
||||
spec: {
|
||||
type: string;
|
||||
@@ -279,9 +298,9 @@ export const LOCATION_ANNOTATION = "backstage.io/managed-by-location";
|
||||
// @public (undocumented)
|
||||
interface LocationEntityV1alpha1 extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: typeof API_VERSION_5[number];
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
// (undocumented)
|
||||
kind: typeof KIND_5;
|
||||
kind: 'Location';
|
||||
// (undocumented)
|
||||
spec: {
|
||||
type?: string;
|
||||
@@ -405,9 +424,9 @@ export const RELATION_PROVIDES_API = "providesApi";
|
||||
// @public (undocumented)
|
||||
interface ResourceEntityV1alpha1 extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: typeof API_VERSION_6[number];
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
// (undocumented)
|
||||
kind: typeof KIND_6;
|
||||
kind: 'Resource';
|
||||
// (undocumented)
|
||||
spec: {
|
||||
type: string;
|
||||
@@ -424,9 +443,6 @@ export { ResourceEntityV1alpha1 }
|
||||
// @public (undocumented)
|
||||
export const resourceEntityV1alpha1Validator: KindValidator;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export function schemaValidator(kind: string, apiVersion: readonly string[], schema: yup.Schema<any>): KindValidator;
|
||||
|
||||
// @public
|
||||
export class SchemaValidEntityPolicy implements EntityPolicy {
|
||||
// (undocumented)
|
||||
@@ -459,9 +475,9 @@ export function stringifyLocationReference(ref: {
|
||||
// @public (undocumented)
|
||||
interface SystemEntityV1alpha1 extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: typeof API_VERSION_7[number];
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
// (undocumented)
|
||||
kind: typeof KIND_7;
|
||||
kind: 'System';
|
||||
// (undocumented)
|
||||
spec: {
|
||||
owner: string;
|
||||
@@ -479,9 +495,9 @@ export const systemEntityV1alpha1Validator: KindValidator;
|
||||
// @public (undocumented)
|
||||
interface TemplateEntityV1alpha1 extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: typeof API_VERSION_8[number];
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
// (undocumented)
|
||||
kind: typeof KIND_8;
|
||||
kind: 'Template';
|
||||
// (undocumented)
|
||||
spec: {
|
||||
type: string;
|
||||
@@ -502,9 +518,9 @@ export const templateEntityV1alpha1Validator: KindValidator;
|
||||
// @public (undocumented)
|
||||
export interface TemplateEntityV1beta2 extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: typeof API_VERSION_9[number];
|
||||
apiVersion: 'backstage.io/v1beta2';
|
||||
// (undocumented)
|
||||
kind: typeof KIND_9;
|
||||
kind: 'Template';
|
||||
// (undocumented)
|
||||
metadata: EntityMeta & {
|
||||
title?: string;
|
||||
@@ -548,9 +564,9 @@ export type UNSTABLE_EntityStatusLevel = 'info' | 'warning' | 'error';
|
||||
// @public (undocumented)
|
||||
interface UserEntityV1alpha1 extends Entity {
|
||||
// (undocumented)
|
||||
apiVersion: typeof API_VERSION_10[number];
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
// (undocumented)
|
||||
kind: typeof KIND_10;
|
||||
kind: 'User';
|
||||
// (undocumented)
|
||||
spec: {
|
||||
profile?: {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@types/yup": "^0.29.8",
|
||||
"ajv": "^7.0.3",
|
||||
"json-schema": "^0.3.0",
|
||||
"typescript-json-schema": "^0.49.0",
|
||||
"lodash": "^4.17.15",
|
||||
"uuid": "^8.0.0",
|
||||
"yup": "^0.29.3"
|
||||
|
||||
@@ -16,14 +16,15 @@
|
||||
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import { EntityName } from '../types';
|
||||
import { EntityEnvelope } from './EntityEnvelope';
|
||||
import { UNSTABLE_EntityStatus } from './EntityStatus';
|
||||
|
||||
/**
|
||||
* The format envelope that's common to all versions/kinds of entity.
|
||||
* The parts of the format that's common to all versions/kinds of entity.
|
||||
*
|
||||
* @see https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/
|
||||
*/
|
||||
export type Entity = {
|
||||
export type Entity = EntityEnvelope & {
|
||||
/**
|
||||
* The version of specification format for this particular entity that
|
||||
* this is written against.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The envelope skeleton parts of an entity - whatever is necessary to be able
|
||||
* to give it a ref and pass to further validation / policy checking.
|
||||
*
|
||||
* @see https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/
|
||||
*/
|
||||
export type EntityEnvelope = {
|
||||
/**
|
||||
* The version of specification format for this particular entity that
|
||||
* this is written against.
|
||||
*/
|
||||
apiVersion: string;
|
||||
|
||||
/**
|
||||
* The high level entity type being described.
|
||||
*/
|
||||
kind: string;
|
||||
|
||||
/**
|
||||
* Metadata related to the entity.
|
||||
*/
|
||||
metadata: {
|
||||
/**
|
||||
* The name of the entity.
|
||||
*
|
||||
* Must be unique within the catalog at any given point in time, for any
|
||||
* given namespace + kind pair.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The namespace that the entity belongs to.
|
||||
*/
|
||||
namespace?: string;
|
||||
};
|
||||
};
|
||||
@@ -27,6 +27,7 @@ export type {
|
||||
EntityRelation,
|
||||
EntityRelationSpec,
|
||||
} from './Entity';
|
||||
export type { EntityEnvelope } from './EntityEnvelope';
|
||||
export type {
|
||||
UNSTABLE_EntityStatus,
|
||||
UNSTABLE_EntityStatusItem,
|
||||
|
||||
@@ -16,17 +16,11 @@
|
||||
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/API.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'API' as const;
|
||||
|
||||
export interface ApiEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
kind: 'API';
|
||||
spec: {
|
||||
type: string;
|
||||
lifecycle: string;
|
||||
@@ -37,8 +31,5 @@ export interface ApiEntityV1alpha1 extends Entity {
|
||||
}
|
||||
|
||||
export const apiEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -16,17 +16,11 @@
|
||||
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/Component.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Component' as const;
|
||||
|
||||
export interface ComponentEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
kind: 'Component';
|
||||
spec: {
|
||||
type: string;
|
||||
lifecycle: string;
|
||||
@@ -40,8 +34,5 @@ export interface ComponentEntityV1alpha1 extends Entity {
|
||||
}
|
||||
|
||||
export const componentEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -16,25 +16,16 @@
|
||||
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/Domain.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Domain' as const;
|
||||
|
||||
export interface DomainEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
kind: 'Domain';
|
||||
spec: {
|
||||
owner: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const domainEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -16,17 +16,11 @@
|
||||
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/Group.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Group' as const;
|
||||
|
||||
export interface GroupEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
kind: 'Group';
|
||||
spec: {
|
||||
type: string;
|
||||
profile?: {
|
||||
@@ -41,8 +35,5 @@ export interface GroupEntityV1alpha1 extends Entity {
|
||||
}
|
||||
|
||||
export const groupEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -16,17 +16,11 @@
|
||||
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/Location.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Location' as const;
|
||||
|
||||
export interface LocationEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
kind: 'Location';
|
||||
spec: {
|
||||
type?: string;
|
||||
target?: string;
|
||||
@@ -35,8 +29,5 @@ export interface LocationEntityV1alpha1 extends Entity {
|
||||
}
|
||||
|
||||
export const locationEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -16,17 +16,11 @@
|
||||
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/Resource.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Resource' as const;
|
||||
|
||||
export interface ResourceEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
kind: 'Resource';
|
||||
spec: {
|
||||
type: string;
|
||||
owner: string;
|
||||
@@ -36,8 +30,5 @@ export interface ResourceEntityV1alpha1 extends Entity {
|
||||
}
|
||||
|
||||
export const resourceEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -16,17 +16,11 @@
|
||||
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/System.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'System' as const;
|
||||
|
||||
export interface SystemEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
kind: 'System';
|
||||
spec: {
|
||||
owner: string;
|
||||
domain?: string;
|
||||
@@ -34,8 +28,5 @@ export interface SystemEntityV1alpha1 extends Entity {
|
||||
}
|
||||
|
||||
export const systemEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -16,18 +16,12 @@
|
||||
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/Template.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import type { JSONSchema } from '../types';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Template' as const;
|
||||
|
||||
export interface TemplateEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
kind: 'Template';
|
||||
spec: {
|
||||
type: string;
|
||||
templater: string;
|
||||
@@ -38,8 +32,5 @@ export interface TemplateEntityV1alpha1 extends Entity {
|
||||
}
|
||||
|
||||
export const templateEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,20 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import type { Entity, EntityMeta } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/Template.v1beta2.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1beta2'] as const;
|
||||
const KIND = 'Template' as const;
|
||||
|
||||
export interface TemplateEntityV1beta2 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
apiVersion: 'backstage.io/v1beta2';
|
||||
kind: 'Template';
|
||||
metadata: EntityMeta & {
|
||||
title?: string;
|
||||
};
|
||||
@@ -46,8 +40,5 @@ export interface TemplateEntityV1beta2 extends Entity {
|
||||
}
|
||||
|
||||
export const templateEntityV1beta2Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -16,17 +16,11 @@
|
||||
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/User.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'User' as const;
|
||||
|
||||
export interface UserEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1';
|
||||
kind: 'User';
|
||||
spec: {
|
||||
profile?: {
|
||||
displayName?: string;
|
||||
@@ -38,8 +32,5 @@ export interface UserEntityV1alpha1 extends Entity {
|
||||
}
|
||||
|
||||
export const userEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { schemaValidator } from './util';
|
||||
export type { KindValidator } from './types';
|
||||
export { apiEntityV1alpha1Validator } from './ApiEntityV1alpha1';
|
||||
export type {
|
||||
ApiEntityV1alpha1 as ApiEntity,
|
||||
@@ -59,6 +57,7 @@ export type {
|
||||
} from './TemplateEntityV1alpha1';
|
||||
export { templateEntityV1beta2Validator } from './TemplateEntityV1beta2';
|
||||
export type { TemplateEntityV1beta2 } from './TemplateEntityV1beta2';
|
||||
export type { KindValidator } from './types';
|
||||
export { userEntityV1alpha1Validator } from './UserEntityV1alpha1';
|
||||
export type {
|
||||
UserEntityV1alpha1 as UserEntity,
|
||||
|
||||
@@ -14,66 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Ajv, { AnySchema } from 'ajv';
|
||||
import * as yup from 'yup';
|
||||
import { entityKindSchemaValidator } from '../validation';
|
||||
import { KindValidator } from './types';
|
||||
|
||||
/**
|
||||
* @deprecated We no longer use yup for the catalog model. This utility method will be removed.
|
||||
*/
|
||||
export function schemaValidator(
|
||||
kind: string,
|
||||
apiVersion: readonly string[],
|
||||
schema: yup.Schema<any>,
|
||||
): KindValidator {
|
||||
// TODO(freben): Left here as a compatibility helper. It would be nicer to
|
||||
// just export the inner validator directly. However, all of the already
|
||||
// exported kind validators have the `KindValidator` signature which is
|
||||
// different. So let's postpone that change until a later time.
|
||||
export function ajvCompiledJsonSchemaValidator(schema: unknown): KindValidator {
|
||||
const validator = entityKindSchemaValidator(schema);
|
||||
return {
|
||||
async check(envelope) {
|
||||
if (kind !== envelope.kind || !apiVersion.includes(envelope.apiVersion)) {
|
||||
return false;
|
||||
}
|
||||
await schema.validate(envelope, { strict: true });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function ajvCompiledJsonSchemaValidator(
|
||||
kind: string,
|
||||
apiVersion: readonly string[],
|
||||
schema: AnySchema,
|
||||
extraSchemas?: AnySchema[],
|
||||
): KindValidator {
|
||||
const ajv = new Ajv({ allowUnionTypes: true });
|
||||
if (extraSchemas) {
|
||||
ajv.addSchema(extraSchemas, undefined, undefined, true);
|
||||
}
|
||||
const validate = ajv.compile(schema);
|
||||
|
||||
return {
|
||||
async check(envelope) {
|
||||
if (kind !== envelope.kind || !apiVersion.includes(envelope.apiVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = validate(envelope);
|
||||
if (result === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const [error] = validate.errors || [];
|
||||
if (!error) {
|
||||
throw new TypeError(`Malformed ${kind}, Unknown error`);
|
||||
}
|
||||
|
||||
throw new TypeError(
|
||||
`Malformed ${kind}, ${error.dataPath || '<root>'} ${error.message}${
|
||||
error.params
|
||||
? ` - ${Object.entries(error.params)
|
||||
.map(([key, val]) => `${key}: ${val}`)
|
||||
.join(', ')}`
|
||||
: ''
|
||||
}`,
|
||||
);
|
||||
async check(data) {
|
||||
return validator(data) === data;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "Entity",
|
||||
"description": "The format envelope that's common to all versions/kinds of entity.",
|
||||
"description": "The parts of the format that's common to all versions/kinds of entity.",
|
||||
"examples": [
|
||||
{
|
||||
"apiVersion": "backstage.io/v1alpha1",
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "EntityEnvelope",
|
||||
"description": "The envelope skeleton parts of an entity - whatever is necessary to be able to give it a ref and pass to further validation / policy checking.",
|
||||
"examples": [
|
||||
{
|
||||
"apiVersion": "backstage.io/v1alpha1",
|
||||
"kind": "Component",
|
||||
"metadata": {
|
||||
"name": "LoremService"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "object",
|
||||
"required": ["apiVersion", "kind", "metadata"],
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"type": "string",
|
||||
"description": "The version of specification format for this particular entity that this is written against.",
|
||||
"minLength": 1,
|
||||
"examples": ["backstage.io/v1alpha1", "my-company.net/v1", "1.0"]
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"description": "The high level entity type being described.",
|
||||
"minLength": 1,
|
||||
"examples": [
|
||||
"API",
|
||||
"Component",
|
||||
"Domain",
|
||||
"Group",
|
||||
"Location",
|
||||
"Resource",
|
||||
"System",
|
||||
"Template",
|
||||
"User"
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the entity. Must be unique within the catalog at any given point in time, for any given namespace + kind pair.",
|
||||
"examples": ["metadata-proxy"],
|
||||
"minLength": 1
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "The namespace that the entity belongs to.",
|
||||
"default": "default",
|
||||
"examples": ["default", "admin"],
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2021 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 Ajv, { Schema, ValidateFunction } from 'ajv';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityEnvelopeSchema from '../schema/EntityEnvelope.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
|
||||
// A local cache of compiled schemas, to avoid duplicate work.
|
||||
// The keys are JSON stringified versions of the schema
|
||||
const compiledSchemaCache = new Map<string, ValidateFunction<unknown>>();
|
||||
|
||||
// The core schemas that others can depend on
|
||||
const refDependencyCandidates = [
|
||||
entityEnvelopeSchema,
|
||||
entitySchema,
|
||||
entityMetaSchema,
|
||||
commonSchema,
|
||||
];
|
||||
|
||||
export function throwAjvError(
|
||||
errors: ValidateFunction<unknown>['errors'],
|
||||
): never {
|
||||
if (!errors?.length) {
|
||||
throw new TypeError('Unknown error');
|
||||
}
|
||||
|
||||
const error = errors[0];
|
||||
throw new TypeError(
|
||||
`${error.dataPath || '<root>'} ${error.message}${
|
||||
error.params
|
||||
? ` - ${Object.entries(error.params)
|
||||
.map(([key, val]) => `${key}: ${val}`)
|
||||
.join(', ')}`
|
||||
: ''
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Compiles the given schema, and makes sure to also grab any core dependencies
|
||||
// that it depends on
|
||||
export function compileAjvSchema(
|
||||
schema: Schema,
|
||||
options: { disableCache?: boolean } = {},
|
||||
): ValidateFunction<unknown> {
|
||||
const disableCache = options?.disableCache ?? false;
|
||||
const cacheKey = disableCache ? '' : JSON.stringify(schema);
|
||||
|
||||
if (!disableCache) {
|
||||
const cached = compiledSchemaCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
const extraSchemas = getExtraSchemas(schema);
|
||||
const ajv = new Ajv({
|
||||
allowUnionTypes: true,
|
||||
allErrors: true,
|
||||
validateSchema: true,
|
||||
});
|
||||
if (extraSchemas.length) {
|
||||
ajv.addSchema(extraSchemas, undefined, undefined, true);
|
||||
}
|
||||
const compiled = ajv.compile(schema);
|
||||
|
||||
if (!disableCache) {
|
||||
compiledSchemaCache.set(cacheKey, compiled);
|
||||
}
|
||||
|
||||
return compiled;
|
||||
}
|
||||
|
||||
// Find refs in the given schema and recursively in all known schemas it
|
||||
// targets, collecting that list of schemas as we go
|
||||
function getExtraSchemas(schema: Schema): Schema[] {
|
||||
if (typeof schema !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const seen = new Set<string>();
|
||||
if (schema.$id) {
|
||||
seen.add(schema.$id);
|
||||
}
|
||||
|
||||
const selected = new Array<Schema>();
|
||||
|
||||
const todo: Schema[] = [schema];
|
||||
while (todo.length) {
|
||||
const current = todo.pop()!;
|
||||
|
||||
for (const ref of getAllRefs(current)) {
|
||||
if (!seen.has(ref)) {
|
||||
seen.add(ref);
|
||||
|
||||
const match = refDependencyCandidates.find(c => c.$id === ref);
|
||||
if (match) {
|
||||
selected.push(match);
|
||||
todo.push(match);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
// Naively step through the entire schema looking for "$ref": "x" pairs. The
|
||||
// resulting iterator may contain duplicates. Ignores fragments, i.e. for a ref
|
||||
// of "a#b", it will just yield "a".
|
||||
function* getAllRefs(schema: Schema): Iterable<string> {
|
||||
const todo: any[] = [schema];
|
||||
while (todo.length) {
|
||||
const current = todo.pop()!;
|
||||
if (typeof current === 'object' && current) {
|
||||
for (const [key, value] of Object.entries(current)) {
|
||||
if (key === '$ref' && typeof value === 'string') {
|
||||
yield value.split('#')[0];
|
||||
} else {
|
||||
todo.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2021 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 { entityEnvelopeSchemaValidator } from './entityEnvelopeSchemaValidator';
|
||||
|
||||
describe('entityEnvelopeSchemaValidator', () => {
|
||||
const validator = entityEnvelopeSchemaValidator();
|
||||
let entity: any;
|
||||
|
||||
beforeEach(() => {
|
||||
entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'test',
|
||||
namespace: 'ns',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('happy path: accepts valid data', () => {
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
//
|
||||
// apiVersion and kind
|
||||
//
|
||||
|
||||
it('rejects wrong root type', () => {
|
||||
expect(() => validator(7)).toThrow(/object/);
|
||||
});
|
||||
|
||||
it('accepts unknown root fields', () => {
|
||||
entity.blah = 7;
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects missing apiVersion', () => {
|
||||
delete entity.apiVersion;
|
||||
expect(() => validator(entity)).toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects bad apiVersion type', () => {
|
||||
entity.apiVersion = 7;
|
||||
expect(() => validator(entity)).toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects empty apiVersion', () => {
|
||||
entity.apiVersion = '';
|
||||
expect(() => validator(entity)).toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects missing kind', () => {
|
||||
delete entity.kind;
|
||||
expect(() => validator(entity)).toThrow(/kind/);
|
||||
});
|
||||
|
||||
it('rejects bad kind type', () => {
|
||||
entity.kind = 7;
|
||||
expect(() => validator(entity)).toThrow(/kind/);
|
||||
});
|
||||
|
||||
it('rejects empty kind', () => {
|
||||
entity.kind = '';
|
||||
expect(() => validator(entity)).toThrow(/kind/);
|
||||
});
|
||||
|
||||
//
|
||||
// metadata
|
||||
//
|
||||
|
||||
it('rejects missing metadata', () => {
|
||||
delete entity.metadata;
|
||||
expect(() => validator(entity)).toThrow(/metadata/);
|
||||
});
|
||||
|
||||
it('rejects bad metadata type', () => {
|
||||
entity.metadata = 7;
|
||||
expect(() => validator(entity)).toThrow(/metadata/);
|
||||
});
|
||||
|
||||
it('rejects missing name', () => {
|
||||
delete entity.metadata.name;
|
||||
expect(() => validator(entity)).toThrow(/name/);
|
||||
});
|
||||
|
||||
it('rejects empty name', () => {
|
||||
entity.metadata.name = '';
|
||||
expect(() => validator(entity)).toThrow(/name/);
|
||||
});
|
||||
|
||||
it('rejects bad name type', () => {
|
||||
entity.metadata.name = 7;
|
||||
expect(() => validator(entity)).toThrow(/name/);
|
||||
});
|
||||
|
||||
it('accepts missing namespace', () => {
|
||||
delete entity.metadata.namespace;
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects empty namespace', () => {
|
||||
entity.metadata.namespace = '';
|
||||
expect(() => validator(entity)).toThrow(/namespace/);
|
||||
});
|
||||
|
||||
it('rejects bad namespace type', () => {
|
||||
entity.metadata.namespace = 7;
|
||||
expect(() => validator(entity)).toThrow(/namespace/);
|
||||
});
|
||||
|
||||
it('accepts unknown metadata fields', () => {
|
||||
entity.metadata.blah = 7;
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2021 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 { Schema } from 'ajv';
|
||||
import { EntityEnvelope } from '../entity/EntityEnvelope';
|
||||
import entityEnvelopeSchema from '../schema/EntityEnvelope.schema.json';
|
||||
import { compileAjvSchema, throwAjvError } from './ajv';
|
||||
|
||||
/**
|
||||
* Creates a validation function that takes some arbitrary data, and either
|
||||
* returns that data cast to an {@link EntityEnvelope} (or the given subtype)
|
||||
* if it matches that schema, or throws a {@link TypeError} describing the
|
||||
* errors.
|
||||
*
|
||||
* Note that this validator is only meant for applying the base schema checks;
|
||||
* it does not take custom policies or additional processor based validation
|
||||
* into account.
|
||||
*
|
||||
* By default, the plain `EntityEnvelope` schema is used. If you pass in your
|
||||
* own, it may contain `$ref` references to the following, which are resolved
|
||||
* automatically for you:
|
||||
*
|
||||
* - EntityEnvelope
|
||||
* - Entity
|
||||
* - EntityMeta
|
||||
* - common#<id>
|
||||
*
|
||||
* @see https://github.com/backstage/backstage/tree/master/packages/catalog-model/src/schema
|
||||
*/
|
||||
export function entityEnvelopeSchemaValidator<
|
||||
T extends EntityEnvelope = EntityEnvelope
|
||||
>(schema?: unknown): (data: unknown) => T {
|
||||
const validate = compileAjvSchema(
|
||||
schema ? (schema as Schema) : entityEnvelopeSchema,
|
||||
);
|
||||
|
||||
return data => {
|
||||
const result = validate(data);
|
||||
if (result === true) {
|
||||
return data as T;
|
||||
}
|
||||
|
||||
throw throwAjvError(validate.errors);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2021 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 { entityKindSchemaValidator } from './entityKindSchemaValidator';
|
||||
import componentSchema from '../schema/kinds/Component.v1alpha1.schema.json';
|
||||
|
||||
describe('entityKindSchemaValidator', () => {
|
||||
const validator = entityKindSchemaValidator(componentSchema);
|
||||
let entity: any;
|
||||
|
||||
beforeEach(() => {
|
||||
entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
uid: 'e01199ab-08cc-44c2-8e19-5c29ded82521',
|
||||
etag: 'lsndfkjsndfkjnsdfkjnsd==',
|
||||
generation: 13,
|
||||
name: 'test',
|
||||
namespace: 'ns',
|
||||
labels: {
|
||||
'backstage.io/custom': 'ValueStuff',
|
||||
},
|
||||
annotations: {
|
||||
'example.com/bindings': 'are-secret',
|
||||
},
|
||||
tags: ['java', 'data'],
|
||||
links: [
|
||||
{
|
||||
url: 'https://example.com',
|
||||
title: 'Website',
|
||||
icon: 'website',
|
||||
},
|
||||
],
|
||||
},
|
||||
spec: {
|
||||
type: 'service',
|
||||
lifecycle: 'production',
|
||||
owner: 'me',
|
||||
},
|
||||
relations: [
|
||||
{ type: 't', target: { kind: 'k', namespace: 'ns', name: 'n' } },
|
||||
],
|
||||
status: {
|
||||
items: [
|
||||
{
|
||||
type: 't',
|
||||
level: 'error',
|
||||
message: 'm',
|
||||
error: { name: 'n', message: 'm', code: '1', stack: 's' },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('works in the happy path', () => {
|
||||
expect(validator(entity)).toBe(entity);
|
||||
});
|
||||
|
||||
it('nicely rejects an unknown kind', () => {
|
||||
entity.kind = 'Unknown';
|
||||
expect(validator(entity)).toBe(false);
|
||||
});
|
||||
|
||||
it('nicely rejects an unknown apiVersion', () => {
|
||||
entity.apiVersion = 'backstage.io/v1alpha7';
|
||||
expect(validator(entity)).toBe(false);
|
||||
});
|
||||
|
||||
it('nicely rejects when both kind and apiVersion mismatch', () => {
|
||||
entity.apiVersion = 'backstage.io/v1alpha7';
|
||||
entity.kind = 'Unknown';
|
||||
expect(validator(entity)).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects when the kind is actually breaking other rules than enum', () => {
|
||||
entity.kind = 7;
|
||||
expect(() => validator(entity)).toThrow(/kind/);
|
||||
});
|
||||
|
||||
it('rejects when the apiVersion is actually breaking other rules than enum', () => {
|
||||
entity.apiVersion = 7;
|
||||
expect(() => validator(entity)).toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects nicely when there is both a nice mismatch and a fatal error', () => {
|
||||
entity.kind = 'Unknown';
|
||||
entity.metadata = 7;
|
||||
expect(validator(entity)).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects on errors in other parts of the schema', () => {
|
||||
entity.spec = 7;
|
||||
expect(() => validator(entity)).toThrow(/spec/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2021 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 { Schema } from 'ajv';
|
||||
import { Entity } from '../entity';
|
||||
import { compileAjvSchema, throwAjvError } from './ajv';
|
||||
|
||||
/**
|
||||
* Creates a validation function that takes some arbitrary data, and either
|
||||
* returns that data cast to a `T` if it matches that schema, or `false` if the
|
||||
* schema apiVersion/kind didn't apply to that data, or throws a
|
||||
* {@link TypeError} describing actual errors.
|
||||
*
|
||||
* This validator is highly specialized, in that it has special treatment of
|
||||
* the `kind` and `apiVersion` root keys. This only works if your schema has
|
||||
* their rule set to `"enum"`:
|
||||
*
|
||||
* <pre>
|
||||
* "apiVersion": {
|
||||
* "enum": ["backstage.io/v1alpha1", "backstage.io/v1beta1"]
|
||||
* },
|
||||
* "kind": {
|
||||
* "enum": ["Group"]
|
||||
* },
|
||||
* </pre>
|
||||
*
|
||||
* In the above example, the created validator will return `false` if and only
|
||||
* if the kind and/or apiVersion mismatch.
|
||||
*
|
||||
* Note that this validator is only meant for applying the base schema checks;
|
||||
* it does not take custom policies or additional processor based validation
|
||||
* into account.
|
||||
*
|
||||
* The given schema may contain `$ref` references to the following, which are
|
||||
* resolved automatically for you:
|
||||
*
|
||||
* - EntityEnvelope
|
||||
* - Entity
|
||||
* - EntityMeta
|
||||
* - common#<id>
|
||||
*
|
||||
* @see https://github.com/backstage/backstage/tree/master/packages/catalog-model/src/schema
|
||||
*/
|
||||
export function entityKindSchemaValidator<T extends Entity>(
|
||||
schema: unknown,
|
||||
): (data: unknown) => T | false {
|
||||
const validate = compileAjvSchema(schema as Schema);
|
||||
|
||||
return data => {
|
||||
const result = validate(data);
|
||||
if (result === true) {
|
||||
return data as T;
|
||||
}
|
||||
|
||||
// Only in the case where kind and/or apiVersion have enum mismatches AND
|
||||
// have NO other errors, we call it a soft error.
|
||||
const softCandidates = validate.errors?.filter(e =>
|
||||
['/kind', '/apiVersion'].includes(e.dataPath),
|
||||
);
|
||||
if (
|
||||
softCandidates?.length &&
|
||||
softCandidates.every(e => e.keyword === 'enum')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw throwAjvError(validate.errors);
|
||||
};
|
||||
}
|
||||
@@ -14,16 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Ajv from 'ajv';
|
||||
import { AnyValidateFunction } from 'ajv/dist/core';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { Entity } from './Entity';
|
||||
import { entitySchemaValidator } from './entitySchemaValidator';
|
||||
|
||||
describe('Entity', () => {
|
||||
describe('entitySchemaValidator', () => {
|
||||
const validator = entitySchemaValidator();
|
||||
let entity: any;
|
||||
let validate: AnyValidateFunction;
|
||||
|
||||
beforeEach(() => {
|
||||
entity = {
|
||||
@@ -69,38 +64,10 @@ describe('Entity', () => {
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const ajv = new Ajv({ allowUnionTypes: true });
|
||||
ajv.addSchema([entityMetaSchema, commonSchema], undefined, undefined, true);
|
||||
validate = ajv.compile(entitySchema);
|
||||
});
|
||||
|
||||
function check(data: unknown): () => true {
|
||||
return () => {
|
||||
const result = validate(data);
|
||||
if (result === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const [error] = validate.errors || [];
|
||||
if (!error) {
|
||||
throw new TypeError('Unknown error');
|
||||
}
|
||||
|
||||
throw new TypeError(
|
||||
`${error.dataPath || '<root>'} ${error.message}${
|
||||
error.params
|
||||
? ` - ${Object.entries(error.params)
|
||||
.map(([key, val]) => `${key}: ${val}`)
|
||||
.join(', ')}`
|
||||
: ''
|
||||
}`,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
it('happy path: accepts valid data', () => {
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
//
|
||||
@@ -108,37 +75,37 @@ describe('Entity', () => {
|
||||
//
|
||||
|
||||
it('rejects wrong root type', () => {
|
||||
expect(check((7 as unknown) as Entity)).toThrow(/object/);
|
||||
expect(() => validator(7)).toThrow(/object/);
|
||||
});
|
||||
|
||||
it('rejects missing apiVersion', () => {
|
||||
delete entity.apiVersion;
|
||||
expect(check(entity)).toThrow(/apiVersion/);
|
||||
expect(() => validator(entity)).toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects bad apiVersion type', () => {
|
||||
entity.apiVersion = 7;
|
||||
expect(check(entity)).toThrow(/apiVersion/);
|
||||
expect(() => validator(entity)).toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects empty apiVersion', () => {
|
||||
entity.apiVersion = '';
|
||||
expect(check(entity)).toThrow(/apiVersion/);
|
||||
expect(() => validator(entity)).toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects missing kind', () => {
|
||||
delete entity.kind;
|
||||
expect(check(entity)).toThrow(/kind/);
|
||||
expect(() => validator(entity)).toThrow(/kind/);
|
||||
});
|
||||
|
||||
it('rejects bad kind type', () => {
|
||||
entity.kind = 7;
|
||||
expect(check(entity)).toThrow(/kind/);
|
||||
expect(() => validator(entity)).toThrow(/kind/);
|
||||
});
|
||||
|
||||
it('rejects empty kind', () => {
|
||||
entity.kind = '';
|
||||
expect(check(entity)).toThrow(/kind/);
|
||||
expect(() => validator(entity)).toThrow(/kind/);
|
||||
});
|
||||
|
||||
//
|
||||
@@ -147,202 +114,202 @@ describe('Entity', () => {
|
||||
|
||||
it('rejects missing metadata', () => {
|
||||
delete entity.metadata;
|
||||
expect(check(entity)).toThrow(/metadata/);
|
||||
expect(() => validator(entity)).toThrow(/metadata/);
|
||||
});
|
||||
|
||||
it('rejects bad metadata type', () => {
|
||||
entity.metadata = 7;
|
||||
expect(check(entity)).toThrow(/metadata/);
|
||||
expect(() => validator(entity)).toThrow(/metadata/);
|
||||
});
|
||||
|
||||
it('accepts missing uid', () => {
|
||||
delete entity.metadata.uid;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad uid type', () => {
|
||||
entity.metadata.uid = 7;
|
||||
expect(check(entity)).toThrow(/uid/);
|
||||
expect(() => validator(entity)).toThrow(/uid/);
|
||||
});
|
||||
|
||||
it('rejects empty uid', () => {
|
||||
entity.metadata.uid = '';
|
||||
expect(check(entity)).toThrow(/uid/);
|
||||
expect(() => validator(entity)).toThrow(/uid/);
|
||||
});
|
||||
|
||||
it('accepts missing etag', () => {
|
||||
delete entity.metadata.etag;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad etag type', () => {
|
||||
entity.metadata.etag = 7;
|
||||
expect(check(entity)).toThrow(/etag/);
|
||||
expect(() => validator(entity)).toThrow(/etag/);
|
||||
});
|
||||
|
||||
it('rejects empty etag', () => {
|
||||
entity.metadata.etag = '';
|
||||
expect(check(entity)).toThrow(/etag/);
|
||||
expect(() => validator(entity)).toThrow(/etag/);
|
||||
});
|
||||
|
||||
it('accepts missing generation', () => {
|
||||
delete entity.metadata.generation;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad generation type', () => {
|
||||
entity.metadata.generation = 'a';
|
||||
expect(check(entity)).toThrow(/generation/);
|
||||
expect(() => validator(entity)).toThrow(/generation/);
|
||||
});
|
||||
|
||||
it('rejects zero generation', () => {
|
||||
entity.metadata.generation = 0;
|
||||
expect(check(entity)).toThrow(/generation/);
|
||||
expect(() => validator(entity)).toThrow(/generation/);
|
||||
});
|
||||
|
||||
it('rejects non-integer generation', () => {
|
||||
entity.metadata.generation = 1.5;
|
||||
expect(check(entity)).toThrow(/generation/);
|
||||
expect(() => validator(entity)).toThrow(/generation/);
|
||||
});
|
||||
|
||||
it('rejects missing name', () => {
|
||||
delete entity.metadata.name;
|
||||
expect(check(entity)).toThrow(/name/);
|
||||
expect(() => validator(entity)).toThrow(/name/);
|
||||
});
|
||||
|
||||
it('rejects bad name type', () => {
|
||||
entity.metadata.name = 7;
|
||||
expect(check(entity)).toThrow(/name/);
|
||||
expect(() => validator(entity)).toThrow(/name/);
|
||||
});
|
||||
|
||||
it('accepts missing namespace', () => {
|
||||
delete entity.metadata.namespace;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad namespace type', () => {
|
||||
entity.metadata.namespace = 7;
|
||||
expect(check(entity)).toThrow(/namespace/);
|
||||
expect(() => validator(entity)).toThrow(/namespace/);
|
||||
});
|
||||
|
||||
it('accepts missing description', () => {
|
||||
delete entity.metadata.description;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad description type', () => {
|
||||
entity.metadata.description = 7;
|
||||
expect(check(entity)).toThrow(/description/);
|
||||
expect(() => validator(entity)).toThrow(/description/);
|
||||
});
|
||||
|
||||
it('accepts missing labels', () => {
|
||||
delete entity.metadata.labels;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts empty labels', () => {
|
||||
entity.metadata.labels = {};
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad labels type', () => {
|
||||
entity.metadata.labels = 7;
|
||||
expect(check(entity)).toThrow(/labels/);
|
||||
expect(() => validator(entity)).toThrow(/labels/);
|
||||
});
|
||||
|
||||
it('accepts missing annotations', () => {
|
||||
delete entity.metadata.annotations;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts empty annotations object', () => {
|
||||
entity.metadata.annotations = {};
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad annotations type', () => {
|
||||
entity.metadata.annotations = 7;
|
||||
expect(check(entity)).toThrow(/annotations/);
|
||||
expect(() => validator(entity)).toThrow(/annotations/);
|
||||
});
|
||||
|
||||
it('rejects bad tags type', () => {
|
||||
entity.metadata.tags = 7;
|
||||
expect(check(entity)).toThrow(/tags/);
|
||||
expect(() => validator(entity)).toThrow(/tags/);
|
||||
});
|
||||
|
||||
it('accepts empty tags', () => {
|
||||
entity.metadata.tags = [];
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects empty tag', () => {
|
||||
entity.metadata.tags[0] = '';
|
||||
expect(check(entity)).toThrow(/tags/);
|
||||
expect(() => validator(entity)).toThrow(/tags/);
|
||||
});
|
||||
|
||||
it('rejects bad tag type', () => {
|
||||
entity.metadata.tags[0] = 7;
|
||||
expect(check(entity)).toThrow(/tags/);
|
||||
expect(() => validator(entity)).toThrow(/tags/);
|
||||
});
|
||||
|
||||
it('accepts missing links', () => {
|
||||
delete entity.metadata.links;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts empty links', () => {
|
||||
entity.metadata.links = [];
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects empty links.url', () => {
|
||||
entity.metadata.links[0].url = '';
|
||||
expect(check(entity)).toThrow(/links/);
|
||||
expect(() => validator(entity)).toThrow(/links/);
|
||||
});
|
||||
|
||||
it('rejects missing links.url', () => {
|
||||
delete entity.metadata.links[0].url;
|
||||
expect(check(entity)).toThrow(/links/);
|
||||
expect(() => validator(entity)).toThrow(/links/);
|
||||
});
|
||||
|
||||
it('rejects bad links.url type', () => {
|
||||
entity.metadata.links[0].url = 7;
|
||||
expect(check(entity)).toThrow(/links/);
|
||||
expect(() => validator(entity)).toThrow(/links/);
|
||||
});
|
||||
|
||||
it('rejects empty links.title', () => {
|
||||
entity.metadata.links[0].title = '';
|
||||
expect(check(entity)).toThrow(/links/);
|
||||
expect(() => validator(entity)).toThrow(/links/);
|
||||
});
|
||||
|
||||
it('accepts missing links.title', () => {
|
||||
delete entity.metadata.links[0].title;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad links.title type', () => {
|
||||
entity.metadata.links[0].title = 7;
|
||||
expect(check(entity)).toThrow(/links/);
|
||||
expect(() => validator(entity)).toThrow(/links/);
|
||||
});
|
||||
|
||||
it('rejects empty links.icon', () => {
|
||||
entity.metadata.links[0].icon = '';
|
||||
expect(check(entity)).toThrow(/links/);
|
||||
expect(() => validator(entity)).toThrow(/links/);
|
||||
});
|
||||
|
||||
it('accepts missing links.icon', () => {
|
||||
delete entity.metadata.links[0].icon;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad links.icon type', () => {
|
||||
entity.metadata.links[0].icon = 7;
|
||||
expect(check(entity)).toThrow(/links/);
|
||||
expect(() => validator(entity)).toThrow(/links/);
|
||||
});
|
||||
|
||||
it('accepts unknown metadata field', () => {
|
||||
entity.metadata.unknown = 7;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
//
|
||||
@@ -351,17 +318,17 @@ describe('Entity', () => {
|
||||
|
||||
it('accepts missing spec', () => {
|
||||
delete entity.spec;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects non-object spec', () => {
|
||||
entity.spec = 7;
|
||||
expect(check(entity)).toThrow(/spec/);
|
||||
expect(() => validator(entity)).toThrow(/spec/);
|
||||
});
|
||||
|
||||
it('accepts unknown spec field', () => {
|
||||
entity.spec.unknown = 7;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
//
|
||||
@@ -370,97 +337,97 @@ describe('Entity', () => {
|
||||
|
||||
it('accepts missing relations', () => {
|
||||
delete entity.relations;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts empty relations', () => {
|
||||
entity.relations = [];
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad relations type', () => {
|
||||
entity.relations = 7;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects missing relations.type', () => {
|
||||
delete entity.relations[0].type;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects empty relations.type', () => {
|
||||
entity.relations[0].type = '';
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects wrong relations.type type', () => {
|
||||
entity.relations[0].type = 7;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects missing relations.target', () => {
|
||||
delete entity.relations[0].target;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects empty relations.target', () => {
|
||||
entity.relations[0].target = '';
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects wrong relations.target type', () => {
|
||||
entity.relations[0].target = 7;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects missing relations.target.kind', () => {
|
||||
delete entity.relations[0].target.kind;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects empty relations.target.kind', () => {
|
||||
entity.relations[0].target.kind = '';
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects wrong relations.target.kind type', () => {
|
||||
entity.relations[0].target.kind = 7;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects missing relations.target.namespace', () => {
|
||||
delete entity.relations[0].target.namespace;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects empty relations.target.namespace', () => {
|
||||
entity.relations[0].target.namespace = '';
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects wrong relations.target.namespace type', () => {
|
||||
entity.relations[0].target.namespace = 7;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects missing relations.target.name', () => {
|
||||
delete entity.relations[0].target.name;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects empty relations.target.name', () => {
|
||||
entity.relations[0].target.name = '';
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects wrong relations.target.name type', () => {
|
||||
entity.relations[0].target.name = 7;
|
||||
expect(check(entity)).toThrow(/relations/);
|
||||
expect(() => validator(entity)).toThrow(/relations/);
|
||||
});
|
||||
|
||||
it('rejects unknown relation field', () => {
|
||||
entity.relations[0].unknown = 7;
|
||||
expect(check(entity)).toThrow(/unknown/);
|
||||
expect(() => validator(entity)).toThrow(/unknown/);
|
||||
});
|
||||
|
||||
//
|
||||
@@ -469,161 +436,161 @@ describe('Entity', () => {
|
||||
|
||||
it('accepts missing status', () => {
|
||||
delete entity.status;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts empty status', () => {
|
||||
entity.status = {};
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad status type', () => {
|
||||
entity.status = 7;
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('accepts missing status.items', () => {
|
||||
delete entity.status.items;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts empty status.items', () => {
|
||||
entity.status.items = [];
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad status.items type', () => {
|
||||
entity.status.items = 7;
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('rejects bad status.items item type', () => {
|
||||
entity.status.items[0] = 7;
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('rejects missing status.items.type', () => {
|
||||
delete entity.status.items[0].type;
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('rejects empty status.items.type', () => {
|
||||
entity.status.items[0].type = '';
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('rejects bad status.items.type type', () => {
|
||||
entity.status.items[0].type = 7;
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('rejects missing status.items.level', () => {
|
||||
delete entity.status.items[0].level;
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('rejects empty status.items.level', () => {
|
||||
entity.status.items[0].level = '';
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('rejects bad status.items.level type', () => {
|
||||
entity.status.items[0].level = 7;
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('rejects bad status.items.level enum', () => {
|
||||
entity.status.items[0].level = 'unknown';
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('rejects missing status.items.message', () => {
|
||||
delete entity.status.items[0].message;
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('accepts empty status.items.message', () => {
|
||||
entity.status.items[0].message = '';
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad status.items.message type', () => {
|
||||
entity.status.items[0].message = 7;
|
||||
expect(check(entity)).toThrow(/status/);
|
||||
expect(() => validator(entity)).toThrow(/status/);
|
||||
});
|
||||
|
||||
it('accepts missing status.items.error', () => {
|
||||
delete entity.status.items[0].error;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects missing status.items.error.name', () => {
|
||||
delete entity.status.items[0].error.name;
|
||||
expect(check(entity)).toThrow(/name/);
|
||||
expect(() => validator(entity)).toThrow(/name/);
|
||||
});
|
||||
|
||||
it('rejects empty status.items.error.name', () => {
|
||||
entity.status.items[0].error.name = '';
|
||||
expect(check(entity)).toThrow(/name/);
|
||||
expect(() => validator(entity)).toThrow(/name/);
|
||||
});
|
||||
|
||||
it('rejects bad status.items.error.name type', () => {
|
||||
entity.status.items[0].error.name = 7;
|
||||
expect(check(entity)).toThrow(/name/);
|
||||
expect(() => validator(entity)).toThrow(/name/);
|
||||
});
|
||||
|
||||
it('rejects missing status.items.error.message', () => {
|
||||
delete entity.status.items[0].error.message;
|
||||
expect(check(entity)).toThrow(/message/);
|
||||
expect(() => validator(entity)).toThrow(/message/);
|
||||
});
|
||||
|
||||
it('accepts empty status.items.error.message', () => {
|
||||
entity.status.items[0].error.message = '';
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad status.items.error.message type', () => {
|
||||
entity.status.items[0].error.message = 7;
|
||||
expect(check(entity)).toThrow(/message/);
|
||||
expect(() => validator(entity)).toThrow(/message/);
|
||||
});
|
||||
|
||||
it('accepts missing status.items.error.code', () => {
|
||||
delete entity.status.items[0].error.code;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts empty status.items.error.code', () => {
|
||||
entity.status.items[0].error.code = '';
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad status.items.error.code type', () => {
|
||||
entity.status.items[0].error.code = 7;
|
||||
expect(check(entity)).toThrow(/code/);
|
||||
expect(() => validator(entity)).toThrow(/code/);
|
||||
});
|
||||
|
||||
it('accepts missing status.items.error.stack', () => {
|
||||
delete entity.status.items[0].error.stack;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts empty status.items.error.stack', () => {
|
||||
entity.status.items[0].error.stack = '';
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects bad status.items.error.stack type', () => {
|
||||
entity.status.items[0].error.stack = 7;
|
||||
expect(check(entity)).toThrow(/stack/);
|
||||
expect(() => validator(entity)).toThrow(/stack/);
|
||||
});
|
||||
|
||||
it('accepts unknown status.items field', () => {
|
||||
entity.status.items[0].unknown = 7;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
|
||||
it('accepts unknown status.items.error field', () => {
|
||||
entity.status.items[0].error.unknown = 7;
|
||||
expect(check(entity)).not.toThrow();
|
||||
expect(() => validator(entity)).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2021 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 { Schema } from 'ajv';
|
||||
import { Entity } from '../entity/Entity';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import { compileAjvSchema, throwAjvError } from './ajv';
|
||||
|
||||
/**
|
||||
* Creates a validation function that takes some arbitrary data, and either
|
||||
* returns that data cast to an {@link Entity} (or the given subtype) if it
|
||||
* matches that schema, or throws a {@link TypeError} describing the errors.
|
||||
*
|
||||
* Note that this validator is only meant for applying the base schema checks;
|
||||
* it does not take custom policies or additional processor based validation
|
||||
* into account.
|
||||
*
|
||||
* By default, the plain `Entity` schema is used. If you pass in your own, it
|
||||
* may contain `$ref` references to the following, which are resolved
|
||||
* automatically for you:
|
||||
*
|
||||
* - EntityEnvelope
|
||||
* - Entity
|
||||
* - EntityMeta
|
||||
* - common#<id>
|
||||
*
|
||||
* @see https://github.com/backstage/backstage/tree/master/packages/catalog-model/src/schema
|
||||
*/
|
||||
export function entitySchemaValidator<T extends Entity = Entity>(
|
||||
schema?: unknown,
|
||||
): (data: unknown) => T {
|
||||
const validate = compileAjvSchema(schema ? (schema as Schema) : entitySchema);
|
||||
|
||||
return data => {
|
||||
const result = validate(data);
|
||||
if (result === true) {
|
||||
return data as T;
|
||||
}
|
||||
|
||||
throw throwAjvError(validate.errors);
|
||||
};
|
||||
}
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
|
||||
export { CommonValidatorFunctions } from './CommonValidatorFunctions';
|
||||
export { entityEnvelopeSchemaValidator } from './entityEnvelopeSchemaValidator';
|
||||
export { entityKindSchemaValidator } from './entityKindSchemaValidator';
|
||||
export { entitySchemaValidator } from './entitySchemaValidator';
|
||||
export { KubernetesValidatorFunctions } from './KubernetesValidatorFunctions';
|
||||
export { makeValidator } from './makeValidator';
|
||||
export type { Validators } from './types';
|
||||
|
||||
@@ -19,7 +19,6 @@ module.exports = {
|
||||
'@spotify/eslint-config-base',
|
||||
'@spotify/eslint-config-typescript',
|
||||
'prettier',
|
||||
'prettier/@typescript-eslint',
|
||||
'plugin:jest/recommended',
|
||||
'plugin:monorepo/recommended',
|
||||
],
|
||||
|
||||
@@ -20,8 +20,6 @@ module.exports = {
|
||||
'@spotify/eslint-config-react',
|
||||
'@spotify/eslint-config-typescript',
|
||||
'prettier',
|
||||
'prettier/react',
|
||||
'prettier/@typescript-eslint',
|
||||
'plugin:jest/recommended',
|
||||
'plugin:monorepo/recommended',
|
||||
],
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"diff": "^5.0.0",
|
||||
"esbuild": "^0.8.56",
|
||||
"eslint": "^7.1.0",
|
||||
"eslint-config-prettier": "^6.0.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-jest": "^24.1.0",
|
||||
@@ -111,7 +111,7 @@
|
||||
"url-loader": "^4.1.0",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-dev-server": "3.11.0",
|
||||
"webpack-node-externals": "^2.5.0",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"yaml": "^1.10.0",
|
||||
"yaml-jest": "^1.0.5",
|
||||
"yml-loader": "^2.1.0",
|
||||
|
||||
@@ -205,7 +205,7 @@ export default async (cmd: Command) => {
|
||||
return chalk.red('Please enter an ID for the plugin');
|
||||
} else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) {
|
||||
return chalk.red(
|
||||
'Plugin IDs must be kebab-cased and contain only letters, digits, and dashes.',
|
||||
'Plugin IDs must be lowercase and contain only letters, digits, and dashes.',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -189,7 +189,7 @@ export default async () => {
|
||||
return chalk.red('Please enter an ID for the plugin');
|
||||
} else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) {
|
||||
return chalk.red(
|
||||
'Plugin IDs must be kebab-cased and contain only letters, digits and dashes.',
|
||||
'Plugin IDs must be lowercase and contain only letters, digits and dashes.',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -124,7 +124,9 @@ export async function collectConfigSchemas(
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(packageNames.map(name => processItem({ name })));
|
||||
await Promise.all(
|
||||
packageNames.map(name => processItem({ name, parentPath: currentDir })),
|
||||
);
|
||||
|
||||
const tsSchemas = compileTsSchemas(tsSchemaPaths);
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ export const SingleSignInPage = ({
|
||||
|
||||
const [autoShowPopup, setAutoShowPopup] = useState<boolean>(auto ?? false);
|
||||
// Defaults to true so that an initial check for existing user session is made
|
||||
const [retry, setRetry] = useState<{} | boolean | undefined>(true);
|
||||
const [retry, setRetry] = useState<{} | boolean | undefined>(undefined);
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
// The SignIn component takes some time to decide whether the user is logged-in or not.
|
||||
@@ -110,6 +110,7 @@ export const SingleSignInPage = ({
|
||||
if (!identity && autoShowPopup) {
|
||||
// Unless auto is set to true, this step should not happen.
|
||||
// When user intentionally clicks the Sign In button, autoShowPopup is set to true
|
||||
setShowLoginPage(true);
|
||||
identity = await authApi.getBackstageIdentity({
|
||||
instantPopup: true,
|
||||
});
|
||||
@@ -138,9 +139,7 @@ export const SingleSignInPage = ({
|
||||
}
|
||||
};
|
||||
|
||||
if (retry) {
|
||||
login();
|
||||
}
|
||||
login();
|
||||
}, [onResult, authApi, retry, autoShowPopup]);
|
||||
|
||||
return showLoginPage ? (
|
||||
|
||||
@@ -99,7 +99,7 @@ export default async (cmd: Command): Promise<void> => {
|
||||
return chalk.red('Please enter a name for the app');
|
||||
} else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) {
|
||||
return chalk.red(
|
||||
'App name must be kebab-cased and contain only letters, digits, and dashes.',
|
||||
'App name must be lowercase and contain only letters, digits, and dashes.',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
@@ -147,9 +147,11 @@ export default async (cmd: Command): Promise<void> => {
|
||||
chalk.green(`🥇 Successfully created ${chalk.cyan(answers.name)}`),
|
||||
);
|
||||
Task.log();
|
||||
Task.section('All set! Now you might want to');
|
||||
Task.log(
|
||||
'See https://backstage.io/docs/tutorials/quickstart-app-auth to know more about enabling auth providers',
|
||||
' Set up the software catalog: https://backstage.io/docs/features/software-catalog/configuration',
|
||||
);
|
||||
Task.log(' Add authentication: https://backstage.io/docs/auth/');
|
||||
Task.log();
|
||||
Task.exit();
|
||||
} catch (error) {
|
||||
|
||||
@@ -34,6 +34,8 @@ backend:
|
||||
#ca: # if you have a CA file and want to verify it you can uncomment this section
|
||||
# $file: <file-path>/ca/server.crt
|
||||
{{/if}}
|
||||
cache:
|
||||
store: memory
|
||||
# workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
|
||||
|
||||
integrations:
|
||||
@@ -62,7 +64,7 @@ techdocs:
|
||||
type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives.
|
||||
|
||||
auth:
|
||||
# see https://backstage.io/docs/tutorials/quickstart-app-auth to know more about enabling auth providers
|
||||
# see https://backstage.io/docs/auth/ to learn about auth providers
|
||||
providers: {}
|
||||
|
||||
scaffolder:
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}",
|
||||
"@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}",
|
||||
"@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}",
|
||||
"@gitbeaker/node": "^28.0.2",
|
||||
"@gitbeaker/node": "^29.2.0",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"dockerode": "^3.2.1",
|
||||
"express": "^4.17.1",
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
getRootLogger,
|
||||
useHotMemoize,
|
||||
notFoundHandler,
|
||||
CacheManager,
|
||||
SingleConnectionDatabaseManager,
|
||||
SingleHostDiscovery,
|
||||
UrlReaders,
|
||||
@@ -34,11 +35,13 @@ function makeCreateEnv(config: Config) {
|
||||
root.info(`Created UrlReader ${reader}`);
|
||||
|
||||
const databaseManager = SingleConnectionDatabaseManager.fromConfig(config);
|
||||
const cacheManager = CacheManager.fromConfig(config);
|
||||
|
||||
return (plugin: string): PluginEnvironment => {
|
||||
const logger = root.child({ type: 'plugin', plugin });
|
||||
const database = databaseManager.forPlugin(plugin);
|
||||
return { logger, database, config, reader, discovery };
|
||||
const cache = cacheManager.forPlugin(plugin);
|
||||
return { logger, database, cache, config, reader, discovery };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
PluginCacheManager,
|
||||
PluginDatabaseManager,
|
||||
PluginEndpointDiscovery,
|
||||
UrlReader,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
export type PluginEnvironment = {
|
||||
logger: Logger;
|
||||
database: PluginDatabaseManager;
|
||||
cache: PluginCacheManager;
|
||||
config: Config;
|
||||
reader: UrlReader
|
||||
discovery: PluginEndpointDiscovery;
|
||||
|
||||
@@ -277,6 +277,9 @@ function createEmitter(logger: Logger, parentEntity: Entity) {
|
||||
return;
|
||||
}
|
||||
if (i.type === 'entity') {
|
||||
// TODO(freben): Perform the most basic validation here
|
||||
// (apiVersion, kind, metadata, metadata.name, metadata.namespace, spec)
|
||||
|
||||
const originLocation = getEntityOriginLocationRef(parentEntity);
|
||||
|
||||
deferredEntities.push({
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import {
|
||||
HeaderIconLinkRow,
|
||||
IconLinkVerticalProps,
|
||||
InfoCardVariants,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
@@ -50,15 +51,23 @@ const useStyles = makeStyles({
|
||||
height: 'calc(100% - 10px)', // for pages without content header
|
||||
marginBottom: '10px',
|
||||
},
|
||||
fullHeightCard: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
gridItemCardContent: {
|
||||
flex: 1,
|
||||
},
|
||||
fullHeightCardContent: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
type AboutCardProps = {
|
||||
/** @deprecated The entity is now grabbed from context instead */
|
||||
entity?: Entity;
|
||||
variant?: 'gridItem';
|
||||
variant?: InfoCardVariants;
|
||||
};
|
||||
|
||||
export function AboutCard({ variant }: AboutCardProps) {
|
||||
@@ -103,8 +112,22 @@ export function AboutCard({ variant }: AboutCardProps) {
|
||||
href: 'api',
|
||||
};
|
||||
|
||||
let cardClass = '';
|
||||
if (variant === 'gridItem') {
|
||||
cardClass = classes.gridItemCard;
|
||||
} else if (variant === 'fullHeight') {
|
||||
cardClass = classes.fullHeightCard;
|
||||
}
|
||||
|
||||
let cardContentClass = '';
|
||||
if (variant === 'gridItem') {
|
||||
cardContentClass = classes.gridItemCardContent;
|
||||
} else if (variant === 'fullHeight') {
|
||||
cardContentClass = classes.fullHeightCardContent;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={variant === 'gridItem' ? classes.gridItemCard : ''}>
|
||||
<Card className={cardClass}>
|
||||
<CardHeader
|
||||
title="About"
|
||||
action={
|
||||
@@ -124,9 +147,7 @@ export function AboutCard({ variant }: AboutCardProps) {
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<CardContent
|
||||
className={variant === 'gridItem' ? classes.gridItemCardContent : ''}
|
||||
>
|
||||
<CardContent className={cardContentClass}>
|
||||
<AboutContent entity={entity} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -60,7 +60,7 @@ export type GithubDeployment = {
|
||||
commit: {
|
||||
abbreviatedOid: string;
|
||||
commitUrl: string;
|
||||
};
|
||||
} | null;
|
||||
creator: {
|
||||
login: string;
|
||||
};
|
||||
|
||||
@@ -66,11 +66,12 @@ export function createStatusColumn(): TableColumn<GithubDeployment> {
|
||||
export function createCommitColumn(): TableColumn<GithubDeployment> {
|
||||
return {
|
||||
title: 'Commit',
|
||||
render: (row: GithubDeployment): JSX.Element => (
|
||||
<Link to={row.commit.commitUrl} target="_blank" rel="noopener">
|
||||
{row.commit.abbreviatedOid}
|
||||
</Link>
|
||||
),
|
||||
render: (row: GithubDeployment) =>
|
||||
row.commit && (
|
||||
<Link to={row.commit.commitUrl} target="_blank" rel="noopener">
|
||||
{row.commit.abbreviatedOid}
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { LatestRunCard } from './Cards';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import { JenkinsApi, jenkinsApiRef } from '../../api';
|
||||
|
||||
describe('<LatestRunCard />', () => {
|
||||
const entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'software',
|
||||
description: 'This is the description',
|
||||
annotations: { JENKINS_ANNOTATION: 'jenkins' },
|
||||
},
|
||||
};
|
||||
|
||||
const jenkinsApi: Partial<JenkinsApi> = {
|
||||
getLastBuild: () => Promise.resolve({ timestamp: 0, result: 'success' }),
|
||||
};
|
||||
|
||||
it('should show success status of latest build', async () => {
|
||||
const apis = ApiRegistry.from([[jenkinsApiRef, jenkinsApi]]);
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<LatestRunCard branch="master" />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(getByText('Completed')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show the appropriate error in case of a connection error', async () => {
|
||||
const jenkinsApiWithError: Partial<JenkinsApi> = {
|
||||
getLastBuild: () => Promise.reject(new Error('Unauthorized')),
|
||||
};
|
||||
|
||||
const apis = ApiRegistry.from([[jenkinsApiRef, jenkinsApiWithError]]);
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<LatestRunCard branch="master" />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(getByText("Error: Can't connect to Jenkins")).toBeInTheDocument();
|
||||
expect(getByText('Unauthorized')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show the appropriate error in case Jenkins project is not found', async () => {
|
||||
const jenkinsApiWithError: Partial<JenkinsApi> = {
|
||||
getLastBuild: () =>
|
||||
Promise.reject({
|
||||
notFound: true,
|
||||
message: 'jenkins-project not found',
|
||||
}),
|
||||
};
|
||||
|
||||
const apis = ApiRegistry.from([[jenkinsApiRef, jenkinsApiWithError]]);
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<EntityProvider entity={entity}>
|
||||
<LatestRunCard branch="master" />
|
||||
</EntityProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(getByText("Error: Can't find Jenkins project")).toBeInTheDocument();
|
||||
expect(getByText('jenkins-project not found')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -17,13 +17,14 @@ import {
|
||||
InfoCard,
|
||||
InfoCardVariants,
|
||||
StructuredMetadataTable,
|
||||
WarningPanel,
|
||||
} from '@backstage/core';
|
||||
import { LinearProgress, Link, makeStyles, Theme } from '@material-ui/core';
|
||||
import ExternalLinkIcon from '@material-ui/icons/Launch';
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import React from 'react';
|
||||
import { JenkinsRunStatus } from '../BuildsPage/lib/Status';
|
||||
import { useBuilds } from '../useBuilds';
|
||||
import { ErrorType, useBuilds } from '../useBuilds';
|
||||
import { useProjectSlugFromEntity } from '../useProjectSlugFromEntity';
|
||||
|
||||
const useStyles = makeStyles<Theme>({
|
||||
@@ -75,6 +76,23 @@ const WidgetContent = ({
|
||||
);
|
||||
};
|
||||
|
||||
const JenkinsApiErrorPanel = ({
|
||||
message,
|
||||
errorType,
|
||||
}: {
|
||||
message: string;
|
||||
errorType: ErrorType;
|
||||
}) => {
|
||||
let title = undefined;
|
||||
if (errorType === ErrorType.CONNECTION_ERROR) {
|
||||
title = "Can't connect to Jenkins";
|
||||
} else if (errorType === ErrorType.NOT_FOUND) {
|
||||
title = "Can't find Jenkins project";
|
||||
}
|
||||
|
||||
return <WarningPanel severity="error" title={title} message={message} />;
|
||||
};
|
||||
|
||||
export const LatestRunCard = ({
|
||||
branch = 'master',
|
||||
variant,
|
||||
@@ -83,11 +101,22 @@ export const LatestRunCard = ({
|
||||
variant?: InfoCardVariants;
|
||||
}) => {
|
||||
const projectName = useProjectSlugFromEntity();
|
||||
const [{ builds, loading }] = useBuilds(projectName, branch);
|
||||
const [{ builds, loading, error }] = useBuilds(projectName, branch);
|
||||
const latestRun = builds ?? {};
|
||||
return (
|
||||
<InfoCard title={`Latest ${branch} build`} variant={variant}>
|
||||
<WidgetContent loading={loading} branch={branch} latestRun={latestRun} />
|
||||
{!error ? (
|
||||
<WidgetContent
|
||||
loading={loading}
|
||||
branch={branch}
|
||||
latestRun={latestRun}
|
||||
/>
|
||||
) : (
|
||||
<JenkinsApiErrorPanel
|
||||
message={error.message}
|
||||
errorType={error.errorType}
|
||||
/>
|
||||
)}
|
||||
</InfoCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,6 +18,11 @@ import { useState } from 'react';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { jenkinsApiRef } from '../api';
|
||||
|
||||
export enum ErrorType {
|
||||
CONNECTION_ERROR,
|
||||
NOT_FOUND,
|
||||
}
|
||||
|
||||
export function useBuilds(projectName: string, branch?: string) {
|
||||
const api = useApi(jenkinsApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
@@ -25,6 +30,10 @@ export function useBuilds(projectName: string, branch?: string) {
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
const [error, setError] = useState<{
|
||||
message: string;
|
||||
errorType: ErrorType;
|
||||
}>();
|
||||
|
||||
const restartBuild = async (buildName: string) => {
|
||||
try {
|
||||
@@ -48,7 +57,10 @@ export function useBuilds(projectName: string, branch?: string) {
|
||||
|
||||
return build || [];
|
||||
} catch (e) {
|
||||
errorApi.post(e);
|
||||
const errorType = e.notFound
|
||||
? ErrorType.NOT_FOUND
|
||||
: ErrorType.CONNECTION_ERROR;
|
||||
setError({ message: e.message, errorType });
|
||||
throw e;
|
||||
}
|
||||
}, [api, errorApi, projectName, branch]);
|
||||
@@ -61,6 +73,7 @@ export function useBuilds(projectName: string, branch?: string) {
|
||||
builds,
|
||||
projectName,
|
||||
total,
|
||||
error,
|
||||
},
|
||||
{
|
||||
builds,
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.3",
|
||||
"@gitbeaker/core": "^28.0.2",
|
||||
"@gitbeaker/node": "^28.0.2",
|
||||
"@gitbeaker/core": "^29.2.0",
|
||||
"@gitbeaker/node": "^29.2.0",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
|
||||
@@ -1689,16 +1689,16 @@
|
||||
resolved "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-5.0.0.tgz#3ba791f37b90e7f6170d252b63aacfcae943c039"
|
||||
integrity sha512-WmKrB/575EJCzbeSJR3YQ5sET5FaizeljLRw1382qVUeGqzuWBgIS+AF5a0FO51uQTrDpoRgvuHC2IWVsgwkkA==
|
||||
|
||||
"@changesets/apply-release-plan@^4.2.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-4.2.0.tgz#f1005815f27c3238f66507e90c6ae14d8fc62b41"
|
||||
integrity sha512-/vt6UwgQldhOw93Gb8llI5OuYGlJt2+U45AfcXsoxzl8gZzCmChGm3vUaQJYbmtL8TbL8OOVXHRIKJJidMNPKw==
|
||||
"@changesets/apply-release-plan@^5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-5.0.0.tgz#11bf168acecbf4cfa2b0e6425160bac5ceeec1c3"
|
||||
integrity sha512-SE+5nPNSKUyUociPnAvnjYSVF+diciEhX9ZHSqKWMlydswCDjiaq9gz67qwWCmwgEgEOz0TS7VrQBoOlzbitvA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.4"
|
||||
"@changesets/config" "^1.5.0"
|
||||
"@changesets/config" "^1.6.0"
|
||||
"@changesets/get-version-range-type" "^0.3.2"
|
||||
"@changesets/git" "^1.0.5"
|
||||
"@changesets/types" "^3.3.0"
|
||||
"@changesets/git" "^1.1.1"
|
||||
"@changesets/types" "^4.0.0"
|
||||
"@manypkg/get-packages" "^1.0.1"
|
||||
detect-indent "^6.0.0"
|
||||
fs-extra "^7.0.1"
|
||||
@@ -1708,36 +1708,36 @@
|
||||
resolve-from "^5.0.0"
|
||||
semver "^5.4.1"
|
||||
|
||||
"@changesets/assemble-release-plan@^4.0.0", "@changesets/assemble-release-plan@^4.1.0":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-4.1.0.tgz#091e5e768dfe51835937e71d1ebaca1c9d6de55b"
|
||||
integrity sha512-dMqe2L5Pn4UGTW89kOuuCuZD3pQFZj1Sxv92ZW4S98sXGsxcb2PdW+PeHbQ7tawkCYCOvzhXxAlN4OdF2DlDKQ==
|
||||
"@changesets/assemble-release-plan@^5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.0.0.tgz#3e57405e5c375e2d933f62e74d1874915e60cd61"
|
||||
integrity sha512-LElDXTCBUkPSmdXlCisoUWw2paX48snatBmw/hKnGiSvnyZqdTIylLojAGQWG0/vOO9v3s/DvJ4hdagIquxJjg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.4"
|
||||
"@changesets/errors" "^0.1.4"
|
||||
"@changesets/get-dependents-graph" "^1.2.0"
|
||||
"@changesets/types" "^3.3.0"
|
||||
"@changesets/get-dependents-graph" "^1.2.1"
|
||||
"@changesets/types" "^4.0.0"
|
||||
"@manypkg/get-packages" "^1.0.1"
|
||||
semver "^5.4.1"
|
||||
|
||||
"@changesets/cli@^2.14.0":
|
||||
version "2.14.1"
|
||||
resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.14.1.tgz#ef6ba9f69e4dd5f6cde4220e032200e3039a52f8"
|
||||
integrity sha512-ydU2ZUP/s7nHQmz8TrliT+kE2dJUpZYKh8MBHrkV6suchCBhT4DmoP4VDF9M4215r18iPbMpE1TjskzXGXB8eQ==
|
||||
version "2.16.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/cli/-/cli-2.16.0.tgz#9f794005d0503efba5e348b929821a1732fd0f0d"
|
||||
integrity sha512-VFkXSyyk/WRjjUoBI7g7cDy09qBjPbBQOloPMEshTzMo/NY9muWHl2yB/FSSkV/6PxGimPtJ7aEJPYfk8HCfXw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.4"
|
||||
"@changesets/apply-release-plan" "^4.2.0"
|
||||
"@changesets/assemble-release-plan" "^4.1.0"
|
||||
"@changesets/config" "^1.5.0"
|
||||
"@changesets/apply-release-plan" "^5.0.0"
|
||||
"@changesets/assemble-release-plan" "^5.0.0"
|
||||
"@changesets/config" "^1.6.0"
|
||||
"@changesets/errors" "^0.1.4"
|
||||
"@changesets/get-dependents-graph" "^1.2.0"
|
||||
"@changesets/get-release-plan" "^2.0.1"
|
||||
"@changesets/git" "^1.1.0"
|
||||
"@changesets/get-dependents-graph" "^1.2.1"
|
||||
"@changesets/get-release-plan" "^3.0.0"
|
||||
"@changesets/git" "^1.1.1"
|
||||
"@changesets/logger" "^0.0.5"
|
||||
"@changesets/pre" "^1.0.4"
|
||||
"@changesets/read" "^0.4.6"
|
||||
"@changesets/types" "^3.3.0"
|
||||
"@changesets/write" "^0.1.3"
|
||||
"@changesets/pre" "^1.0.6"
|
||||
"@changesets/read" "^0.4.7"
|
||||
"@changesets/types" "^4.0.0"
|
||||
"@changesets/write" "^0.1.4"
|
||||
"@manypkg/get-packages" "^1.0.1"
|
||||
"@types/semver" "^6.0.0"
|
||||
boxen "^1.3.0"
|
||||
@@ -1756,15 +1756,15 @@
|
||||
term-size "^2.1.0"
|
||||
tty-table "^2.8.10"
|
||||
|
||||
"@changesets/config@^1.2.0", "@changesets/config@^1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/config/-/config-1.5.0.tgz#6d58b01e45916318d3f39e9cde86a5d69dc58ed8"
|
||||
integrity sha512-Bl9nLVYcwFCpd9jpzcOsExZk1NuTYX20D2YWHCdS1xll3W0yOdSUlWLUCCfugN1l3+yTR6iDW6q9o6vpCevWvA==
|
||||
"@changesets/config@^1.6.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/config/-/config-1.6.0.tgz#2cd9426b9d4212534d2b31c51de43280b76d3df4"
|
||||
integrity sha512-vMY/OpMFSDC2crDKb5Nq2kMX9hozcXL4dY5Rr+a1JQ044Rz+jqjJPpdTP2yQ+j7qmeGcUTvwjJoEMeekYwfqhg==
|
||||
dependencies:
|
||||
"@changesets/errors" "^0.1.4"
|
||||
"@changesets/get-dependents-graph" "^1.2.0"
|
||||
"@changesets/get-dependents-graph" "^1.2.1"
|
||||
"@changesets/logger" "^0.0.5"
|
||||
"@changesets/types" "^3.3.0"
|
||||
"@changesets/types" "^4.0.0"
|
||||
"@manypkg/get-packages" "^1.0.1"
|
||||
fs-extra "^7.0.1"
|
||||
micromatch "^4.0.2"
|
||||
@@ -1776,28 +1776,28 @@
|
||||
dependencies:
|
||||
extendable-error "^0.1.5"
|
||||
|
||||
"@changesets/get-dependents-graph@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.2.0.tgz#6986927a5fec60bc6fcc76f966efae2ac30c05ed"
|
||||
integrity sha512-96NInEKpEZH8KvmXyh42PynXVAdq3kQ9VjAeswHtJ3umUCeTF42b/KVXaov+5P1RNnaUVtRuEwzs4syGuowDTw==
|
||||
"@changesets/get-dependents-graph@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.2.1.tgz#462908693dc3a354622e43f85a764b74b5bb53af"
|
||||
integrity sha512-vJOibo9SkqhVbgfq5AHIlQ7tzkYQIXh3tPAnlNLy2bPZsU+SByd74GaxHYWt1zOBlncU25WKrIM6J7XBB+GVUg==
|
||||
dependencies:
|
||||
"@changesets/types" "^3.3.0"
|
||||
"@changesets/types" "^4.0.0"
|
||||
"@manypkg/get-packages" "^1.0.1"
|
||||
chalk "^2.1.0"
|
||||
fs-extra "^7.0.1"
|
||||
semver "^5.4.1"
|
||||
|
||||
"@changesets/get-release-plan@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-2.0.1.tgz#b95d8f1a3cc719ff4b42b9b9aae72458d8787c13"
|
||||
integrity sha512-+x5N9/Iaka+c0Kq7+3JsboMNyffKYlWPmdm+VeafDcMwJFhBDkxm84qaCJ93ydmnzQOTig6gYVqw0k8BbHExyQ==
|
||||
"@changesets/get-release-plan@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.0.tgz#55efc01db2e24bd7a88e703956eb2f6c4a79054f"
|
||||
integrity sha512-7VLiqpcWZyjwIXYgkubBC/9cdwqUJEhLMRT9/Y9+ctHqrpsXmJg15QQPTOh3HT9yGN5fJPL1WwuZkc1HXUhK0g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.4"
|
||||
"@changesets/assemble-release-plan" "^4.0.0"
|
||||
"@changesets/config" "^1.2.0"
|
||||
"@changesets/pre" "^1.0.4"
|
||||
"@changesets/read" "^0.4.6"
|
||||
"@changesets/types" "^3.1.0"
|
||||
"@changesets/assemble-release-plan" "^5.0.0"
|
||||
"@changesets/config" "^1.6.0"
|
||||
"@changesets/pre" "^1.0.6"
|
||||
"@changesets/read" "^0.4.7"
|
||||
"@changesets/types" "^4.0.0"
|
||||
"@manypkg/get-packages" "^1.0.1"
|
||||
|
||||
"@changesets/get-version-range-type@^0.3.2":
|
||||
@@ -1805,14 +1805,14 @@
|
||||
resolved "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.3.2.tgz#8131a99035edd11aa7a44c341cbb05e668618c67"
|
||||
integrity sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==
|
||||
|
||||
"@changesets/git@^1.0.5", "@changesets/git@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/git/-/git-1.1.0.tgz#ecaa8058ac87b450c09da0e74b68e6854cd0742c"
|
||||
integrity sha512-f/2rQynT+JiAL/V0V/GQdXhLkcb86ELg3UwH3fQO4wVdfUbE9NHIHq9ohJdH1Ymh0Lv48F/b38aWZ5v2sKiF3w==
|
||||
"@changesets/git@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/@changesets/git/-/git-1.1.1.tgz#f444d3ff3604acb6949560656c9ef330485a5fa3"
|
||||
integrity sha512-Z12TcKwgU33YE3r76cyU+X81RchOXljDZ5s3G2u0Zd+ODyrwlDb91IO55+6R0Ha6ouPz8ioont0gA70c1RFngg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.4"
|
||||
"@changesets/errors" "^0.1.4"
|
||||
"@changesets/types" "^3.1.1"
|
||||
"@changesets/types" "^4.0.0"
|
||||
"@manypkg/get-packages" "^1.0.1"
|
||||
is-subdir "^1.1.1"
|
||||
spawndamnit "^2.0.0"
|
||||
@@ -1824,54 +1824,54 @@
|
||||
dependencies:
|
||||
chalk "^2.1.0"
|
||||
|
||||
"@changesets/parse@^0.3.6":
|
||||
version "0.3.7"
|
||||
resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.7.tgz#1368136e2b83d5cff11b4d383a3032723530db99"
|
||||
integrity sha512-8yqKulslq/7V2VRBsJqPgjnZMoehYqhJm5lEOXJPZ2rcuSdyj8+p/2vq2vRDBJT2m0rP+C9G8DujsGYQIFZezw==
|
||||
"@changesets/parse@^0.3.8":
|
||||
version "0.3.8"
|
||||
resolved "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.8.tgz#0bb244eccb35cb301168f85684bb03389c59341d"
|
||||
integrity sha512-0S7Dc7XbMOKamBtd48vVuWL2aFZyaglw6lJsXNddn9forFf8oMKMmdyJ/HQPyeEChDDOhDF1/ya7m/zpt4Dk4w==
|
||||
dependencies:
|
||||
"@changesets/types" "^3.0.0"
|
||||
"@changesets/types" "^4.0.0"
|
||||
js-yaml "^3.13.1"
|
||||
|
||||
"@changesets/pre@^1.0.4":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.5.tgz#91e5e3b31b4a85ce37de72f6511a786f62f29b51"
|
||||
integrity sha512-p43aAQY3aijhDnBLCriPao5YArlRjD4mSHRJq9PsBhljVLWqQQXcn6seSd77d+bD1tATLhB8tQ2eYoxMtMydXQ==
|
||||
"@changesets/pre@^1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.6.tgz#45700cf18274b35b2296000befe7fe4df8ff046f"
|
||||
integrity sha512-ZwFFQLjhTmA4hj8+Cf9pm6nD9Tp+AiBz1wJLaGum4Ae1fPXMwDnJfHknFUTytqZBlC0gHkiGSj6QkUuetWvckg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
"@babel/runtime" "^7.10.4"
|
||||
"@changesets/errors" "^0.1.4"
|
||||
"@changesets/types" "^3.0.0"
|
||||
"@changesets/types" "^4.0.0"
|
||||
"@manypkg/get-packages" "^1.0.1"
|
||||
fs-extra "^7.0.1"
|
||||
|
||||
"@changesets/read@^0.4.6":
|
||||
version "0.4.6"
|
||||
resolved "https://registry.npmjs.org/@changesets/read/-/read-0.4.6.tgz#1c03e709a870a070fc95490ffa696297d23458f7"
|
||||
integrity sha512-rOd8dsF/Lgyy2SYlDalb3Ts/meDI2AcKPXYhSXIW3k6+ZLlj6Pt+nmgV5Ut8euyH7loibklNTDemfvMffF4xig==
|
||||
"@changesets/read@^0.4.7":
|
||||
version "0.4.7"
|
||||
resolved "https://registry.npmjs.org/@changesets/read/-/read-0.4.7.tgz#5a32ae7092330fba31eaec4c83321bb936605766"
|
||||
integrity sha512-E70QrYQpSCMF0nC0dlPU7i6A9zht+8zkQczrKMbOUwDVrfidcvgojxfuJSQbzptYSb9OKYh8GOLd+bsq9+uO9Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
"@changesets/git" "^1.0.5"
|
||||
"@babel/runtime" "^7.10.4"
|
||||
"@changesets/git" "^1.1.1"
|
||||
"@changesets/logger" "^0.0.5"
|
||||
"@changesets/parse" "^0.3.6"
|
||||
"@changesets/types" "^3.0.0"
|
||||
"@changesets/parse" "^0.3.8"
|
||||
"@changesets/types" "^4.0.0"
|
||||
chalk "^2.1.0"
|
||||
fs-extra "^7.0.1"
|
||||
p-filter "^2.1.0"
|
||||
|
||||
"@changesets/types@^3.0.0", "@changesets/types@^3.1.0", "@changesets/types@^3.1.1", "@changesets/types@^3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/types/-/types-3.3.0.tgz#04cd8184b2d2da760667bd89bf9b930938dbd96e"
|
||||
integrity sha512-rJamRo+OD/MQekImfIk07JZwYSB18iU6fYL8xOg0gfAiTh1a1+OlR1fPIxm55I7RsWw812is2YcPPwXdIewrhA==
|
||||
"@changesets/types@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/@changesets/types/-/types-4.0.0.tgz#635f804546b0a96ecc0ca3f26403a6782a3dc938"
|
||||
integrity sha512-whLmPx2wgJRoOtxVZop+DJ71z1gTSkij7osiHgN+pe//FiE6bb4ffvBBb0rACs2cUPfAkWxgSPzqkECgKS1jvQ==
|
||||
|
||||
"@changesets/write@^0.1.3":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.npmjs.org/@changesets/write/-/write-0.1.3.tgz#00ae575af50274773d7493e77fb96838a08ad8ad"
|
||||
integrity sha512-q79rbwlVmTNKP9O6XxcMDj81CEOn/kQHbTFdRleW0tFUv98S1EyEAE9vLPPzO6WnQipHnaozxB1zMhHy0aQn8Q==
|
||||
"@changesets/write@^0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.npmjs.org/@changesets/write/-/write-0.1.4.tgz#5828ecc70c48d0e8696c5f13fe06b730cdfde6f2"
|
||||
integrity sha512-uco+vS3mo2JqflLciIU707har+6AEFOeP8pgu3vVC1M2WcKukQgR1KylHFqZJxKQWahf8mQnuUSbgR4yJQuhmA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.4"
|
||||
"@changesets/types" "^3.0.0"
|
||||
"@babel/runtime" "^7.10.4"
|
||||
"@changesets/types" "^4.0.0"
|
||||
fs-extra "^7.0.1"
|
||||
human-id "^1.0.2"
|
||||
prettier "^1.18.2"
|
||||
prettier "^1.19.1"
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.4"
|
||||
@@ -2093,7 +2093,7 @@
|
||||
dependencies:
|
||||
yaml-ast-parser "0.0.43"
|
||||
|
||||
"@gitbeaker/core@^28.0.2", "@gitbeaker/core@^28.2.0":
|
||||
"@gitbeaker/core@^28.0.2":
|
||||
version "28.2.0"
|
||||
resolved "https://registry.npmjs.org/@gitbeaker/core/-/core-28.2.0.tgz#87e6f789faf55d726f75d05e470020e4bbb8e08c"
|
||||
integrity sha512-GfXknRVtdDPqt0WfxILbR4AOb/yPyBC9jDtPSh1+B8JB5HP+ursA70RJnGKFfP/aOHyikJE1MH/sdA7QBbRAKg==
|
||||
@@ -2103,14 +2103,24 @@
|
||||
li "^1.3.0"
|
||||
xcase "^2.0.1"
|
||||
|
||||
"@gitbeaker/node@^28.0.2":
|
||||
version "28.2.0"
|
||||
resolved "https://registry.npmjs.org/@gitbeaker/node/-/node-28.2.0.tgz#7c18630af5d0bcdf6e8d986f7c6265c3068bc9b3"
|
||||
integrity sha512-rtaXEEwL2aX33FDD427gj+KHinJJoVvaii44R9QyleqvoHLqUx3UUGUkFpbbLpL/lVg4t4pjze7dkRjvig1EMQ==
|
||||
"@gitbeaker/core@^29.2.0":
|
||||
version "29.2.0"
|
||||
resolved "https://registry.npmjs.org/@gitbeaker/core/-/core-29.2.0.tgz#d1ad60e47e03fe9dcc05ce73099cbd506865ec77"
|
||||
integrity sha512-VvJMzAquoOAjHX/MGfFuXJkWtxOlDeY7Q18gHYQRL6ON9a1+L8aYRIzoCAWCemAKuMdP1lAU/29Aw7ehfwJAsg==
|
||||
dependencies:
|
||||
"@gitbeaker/core" "^28.2.0"
|
||||
"@gitbeaker/requester-utils" "^28.2.0"
|
||||
got "^11.7.0"
|
||||
"@gitbeaker/requester-utils" "^29.2.0"
|
||||
form-data "^4.0.0"
|
||||
li "^1.3.0"
|
||||
xcase "^2.0.1"
|
||||
|
||||
"@gitbeaker/node@^29.2.0":
|
||||
version "29.2.0"
|
||||
resolved "https://registry.npmjs.org/@gitbeaker/node/-/node-29.2.0.tgz#62e32df1f404b87c70bb8b8bda77f8ac5ea4cfcb"
|
||||
integrity sha512-WnFJs5aCUfBZEOUa2tbzq4Swh8KGyy7IlNn3KVsRpSi+IIkzGNIjJW585GOqT1nUxYN8mNZil5Q3TjQyhaRisA==
|
||||
dependencies:
|
||||
"@gitbeaker/core" "^29.2.0"
|
||||
"@gitbeaker/requester-utils" "^29.2.0"
|
||||
got "^11.8.2"
|
||||
xcase "^2.0.1"
|
||||
|
||||
"@gitbeaker/requester-utils@^28.2.0":
|
||||
@@ -2122,6 +2132,15 @@
|
||||
query-string "^7.0.0"
|
||||
xcase "^2.0.1"
|
||||
|
||||
"@gitbeaker/requester-utils@^29.2.0":
|
||||
version "29.2.0"
|
||||
resolved "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-29.2.0.tgz#8b69da14dcf4c719e4a476167fcc47173ba44460"
|
||||
integrity sha512-B/xrNmcgCVFUPAA6EjGZ39o//VAVUCaI15tDRl3LCx0hLxce2JOAZNaKKlTqe+4F6xNYIkcuRl28IQS9BPcaAg==
|
||||
dependencies:
|
||||
form-data "^4.0.0"
|
||||
query-string "^7.0.0"
|
||||
xcase "^2.0.1"
|
||||
|
||||
"@google-cloud/common@^0.32.0":
|
||||
version "0.32.1"
|
||||
resolved "https://registry.npmjs.org/@google-cloud/common/-/common-0.32.1.tgz#6a32c340172cea3db6674d0e0e34e78740a0073f"
|
||||
@@ -9035,15 +9054,15 @@ browserslist@4.14.2:
|
||||
node-releases "^1.1.61"
|
||||
|
||||
browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.15.0:
|
||||
version "4.15.0"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.15.0.tgz#3d48bbca6a3f378e86102ffd017d9a03f122bdb0"
|
||||
integrity sha512-IJ1iysdMkGmjjYeRlDU8PQejVwxvVO5QOfXH7ylW31GO6LwNRSmm/SgRXtNsEXqMLl2e+2H5eEJ7sfynF8TCaQ==
|
||||
version "4.16.6"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
|
||||
integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001164"
|
||||
colorette "^1.2.1"
|
||||
electron-to-chromium "^1.3.612"
|
||||
caniuse-lite "^1.0.30001219"
|
||||
colorette "^1.2.2"
|
||||
electron-to-chromium "^1.3.723"
|
||||
escalade "^3.1.1"
|
||||
node-releases "^1.1.67"
|
||||
node-releases "^1.1.71"
|
||||
|
||||
bs-logger@0.x:
|
||||
version "0.2.6"
|
||||
@@ -9411,10 +9430,10 @@ caniuse-api@^3.0.0:
|
||||
lodash.memoize "^4.1.2"
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001164:
|
||||
version "1.0.30001228"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz"
|
||||
integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001219:
|
||||
version "1.0.30001230"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71"
|
||||
integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==
|
||||
|
||||
canvas@^2.6.1:
|
||||
version "2.7.0"
|
||||
@@ -12025,10 +12044,10 @@ electron-to-chromium@^1.3.564:
|
||||
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.685.tgz#f636d17c9c232c925f8bbfbff4f86303da091ff9"
|
||||
integrity sha512-C3oFZNkJ8lz85ADqr3hzpjBc2ciejMRN2SCd/D0hwcqpr6MGxfdN/j89VN6l+ERTuCUvhg0VYsf40Q4qTz4bhQ==
|
||||
|
||||
electron-to-chromium@^1.3.612:
|
||||
version "1.3.620"
|
||||
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.620.tgz#c6f36a7e398acc9d7d12743a6f58d536fbc58700"
|
||||
integrity sha512-YbgWXUR2Mu+Fp6rm3GZ5YJdNo8SgZKLUTNSl2PNvdOcM8OIz07jRJnRkIaV9vdszFv9UUuGChh19w9qSuoLJgw==
|
||||
electron-to-chromium@^1.3.723:
|
||||
version "1.3.739"
|
||||
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz#f07756aa92cabd5a6eec6f491525a64fe62f98b9"
|
||||
integrity sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A==
|
||||
|
||||
elegant-spinner@^1.0.1:
|
||||
version "1.0.1"
|
||||
@@ -12425,12 +12444,10 @@ escodegen@^1.14.1, escodegen@^1.9.1:
|
||||
optionalDependencies:
|
||||
source-map "~0.6.1"
|
||||
|
||||
eslint-config-prettier@^6.0.0:
|
||||
version "6.14.0"
|
||||
resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.14.0.tgz#390e7863a8ae99970981933826476169285b3a27"
|
||||
integrity sha512-DbVwh0qZhAC7CNDWcq8cBdK6FcVHiMTKmCypOPWeZkp9hJ8xYwTaWSa6bb6cjfi8KOeJy0e9a8Izxyx+O4+gCQ==
|
||||
dependencies:
|
||||
get-stdin "^6.0.0"
|
||||
eslint-config-prettier@^8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a"
|
||||
integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==
|
||||
|
||||
eslint-formatter-friendly@^7.0.0:
|
||||
version "7.0.0"
|
||||
@@ -13928,11 +13945,6 @@ get-stdin@^4.0.1:
|
||||
resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||
integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=
|
||||
|
||||
get-stdin@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
|
||||
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
|
||||
|
||||
get-stdin@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53"
|
||||
@@ -14349,7 +14361,7 @@ google-p12-pem@^3.0.3:
|
||||
dependencies:
|
||||
node-forge "^0.10.0"
|
||||
|
||||
got@^11.5.2, got@^11.7.0, got@^11.8.0:
|
||||
got@^11.5.2, got@^11.8.0, got@^11.8.2:
|
||||
version "11.8.2"
|
||||
resolved "https://registry.npmjs.org/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599"
|
||||
integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==
|
||||
@@ -19421,10 +19433,10 @@ node-releases@^1.1.61:
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb"
|
||||
integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==
|
||||
|
||||
node-releases@^1.1.67:
|
||||
version "1.1.67"
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz#28ebfcccd0baa6aad8e8d4d8fe4cbc49ae239c12"
|
||||
integrity sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==
|
||||
node-releases@^1.1.71:
|
||||
version "1.1.72"
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
|
||||
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
|
||||
|
||||
node-request-interceptor@^0.3.5:
|
||||
version "0.3.5"
|
||||
@@ -21411,7 +21423,7 @@ prepend-http@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
||||
|
||||
prettier@^1.18.2, prettier@^1.19.1:
|
||||
prettier@^1.19.1:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||
@@ -26571,10 +26583,10 @@ webpack-log@^2.0.0:
|
||||
ansi-colors "^3.0.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
webpack-node-externals@^2.5.0:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-2.5.2.tgz#178e017a24fec6015bc9e672c77958a6afac861d"
|
||||
integrity sha512-aHdl/y2N7PW2Sx7K+r3AxpJO+aDMcYzMQd60Qxefq3+EwhewSbTBqNumOsCE1JsCUNoyfGj5465N0sSf6hc/5w==
|
||||
webpack-node-externals@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917"
|
||||
integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==
|
||||
|
||||
webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
|
||||
version "1.4.3"
|
||||
|
||||