Merge branch 'master' of github.com:backstage/backstage into scaffolder-examples
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Fixed an issue where the CLI would fail to function when there was a mix of workspace and non-workspace versions of the same package in `yarn.lock` when using Yarn 3.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
The frontend serve task now filters out allowed package duplicates during its package check, just like `versions:bump` and `versions:check`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Fixed bug in `EntityTagPicker` that filtered on unavailable tags for the selected kind.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/repo-tools': patch
|
||||
---
|
||||
|
||||
Move some dependencies as `peerDependencies` because we need to always use same version as in `api-extractor`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
add `onChange` handler to`Stepper` component
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-code-coverage-backend': patch
|
||||
---
|
||||
|
||||
`RouterOptions` now accepts an optional `catalogApi` argument, allowing custom catalog clients to be used. This is leveraged in the local standalone development runner to pass in a catalog client that returns fake data.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend-module-elasticsearch': patch
|
||||
---
|
||||
|
||||
Fixed a bug that could cause an indexing process to silently fail, timeout, and accumulate stale indices.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend-module-elasticsearch': patch
|
||||
---
|
||||
|
||||
Fixed a bug that could cause the backstage backend to unexpectedly terminate when client errors were encountered during the indexing process.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend-module-elasticsearch': patch
|
||||
---
|
||||
|
||||
Improved index throughput by optimizing when and how many documents were made available to the bulk client.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': minor
|
||||
'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch
|
||||
'@backstage/plugin-scaffolder-backend-module-rails': patch
|
||||
'@backstage/plugin-scaffolder-backend-module-yeoman': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Added `catalogFilter` field to OwnerPicker and EntityPicker components to support filtering options by any field(s) of an entity.
|
||||
|
||||
The `allowedKinds` field has been deprecated. Use `catalogFilter` instead. This field allows users to specify a filter on the shape of [EntityFilterQuery](https://github.com/backstage/backstage/blob/774c42003782121d3d6b2aa5f2865d53370c160e/packages/catalog-client/src/types/api.ts#L74), which can be passed into the CatalogClient. See examples below:
|
||||
|
||||
- Get all entities of kind `Group`
|
||||
|
||||
```yaml
|
||||
owner:
|
||||
title: Owner
|
||||
type: string
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
catalogFilter:
|
||||
- kind: Group
|
||||
```
|
||||
|
||||
- Get entities of kind `Group` and spec.type `team`
|
||||
```yaml
|
||||
owner:
|
||||
title: Owner
|
||||
type: string
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
catalogFilter:
|
||||
- kind: Group
|
||||
spec.type: team
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-ilert': patch
|
||||
---
|
||||
|
||||
fixed error on service page not showing if historical uptime was disabled on a service
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
The `repo test`, `repo lint`, and `repo build` commands will now analyze `yarn.lock` for dependency changes when searching for changed packages. This allows you to use the `--since <ref>` flag even if you have `yarn.lock` changes.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-explore': patch
|
||||
---
|
||||
|
||||
Makes the `GroupsDiagram` not grown on screen on its own.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Workspace ranges are no longer considered invalid by version commands.
|
||||
@@ -8,11 +8,11 @@ x-uffizzi:
|
||||
services:
|
||||
backstage:
|
||||
image: '${BACKSTAGE_IMAGE}'
|
||||
|
||||
environment:
|
||||
POSTGRES_HOST: localhost
|
||||
POSTGRES_HOST: db
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: example
|
||||
GITHUB_TOKEN: abc
|
||||
NODE_ENV: production
|
||||
deploy:
|
||||
resources:
|
||||
@@ -21,7 +21,7 @@ services:
|
||||
entrypoint: '/bin/sh'
|
||||
command:
|
||||
- '-c'
|
||||
- "APP_CONFIG_app_baseUrl=$$UFFIZZI_URL APP_CONFIG_backend_baseUrl=$$UFFIZZI_URL APP_CONFIG_auth_environment='production' node packages/backend --config app-config.yaml --config app-config.production.yaml"
|
||||
- "APP_CONFIG_app_baseUrl=$$UFFIZZI_URL APP_CONFIG_backend_baseUrl=$$UFFIZZI_URL APP_CONFIG_auth_environment='production' node packages/backend --config app-config.yaml"
|
||||
|
||||
db:
|
||||
image: postgres
|
||||
|
||||
@@ -2,9 +2,258 @@ app:
|
||||
title: Backstage Uffizzi Environment
|
||||
baseUrl: ${UFFIZZI_URL}
|
||||
|
||||
organization:
|
||||
name: My Company
|
||||
|
||||
backend:
|
||||
baseUrl: ${UFFIZZI_URL}
|
||||
auth:
|
||||
keys:
|
||||
# random mock key for uffizi deployments
|
||||
- secret: 5TXvdjVZFxF7qf9K5RAYRDoGrLzJooqa
|
||||
listen:
|
||||
port: 7007
|
||||
database:
|
||||
client: better-sqlite3
|
||||
connection: ':memory:'
|
||||
cache:
|
||||
store: memory
|
||||
cors:
|
||||
origin: ${UFFIZZI_URL}
|
||||
methods: [GET, POST, PUT, DELETE]
|
||||
methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
|
||||
credentials: true
|
||||
csp:
|
||||
connect-src: ["'self'", 'http:', 'https:']
|
||||
# Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
|
||||
# Default Helmet Content-Security-Policy values can be removed by setting the key to false
|
||||
|
||||
auth:
|
||||
environment: production
|
||||
providers: {}
|
||||
|
||||
catalog:
|
||||
locations:
|
||||
- type: url
|
||||
target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml
|
||||
|
||||
proxy:
|
||||
'/circleci/api':
|
||||
target: https://circleci.com/api/v1.1
|
||||
headers:
|
||||
Circle-Token: ${CIRCLECI_AUTH_TOKEN}
|
||||
|
||||
'/jenkins/api':
|
||||
target: http://localhost:8080
|
||||
headers:
|
||||
Authorization: ${JENKINS_BASIC_AUTH_HEADER}
|
||||
|
||||
'/travisci/api':
|
||||
target: https://api.travis-ci.com
|
||||
changeOrigin: true
|
||||
headers:
|
||||
Authorization: ${TRAVISCI_AUTH_TOKEN}
|
||||
travis-api-version: '3'
|
||||
|
||||
'/newrelic/apm/api':
|
||||
target: https://api.newrelic.com/v2
|
||||
headers:
|
||||
X-Api-Key: ${NEW_RELIC_REST_API_KEY}
|
||||
|
||||
'/newrelic/api':
|
||||
target: https://api.newrelic.com
|
||||
headers:
|
||||
X-Api-Key: ${NEW_RELIC_USER_KEY}
|
||||
|
||||
'/pagerduty':
|
||||
target: https://api.pagerduty.com
|
||||
headers:
|
||||
Authorization: Token token=${PAGERDUTY_TOKEN}
|
||||
|
||||
'/buildkite/api':
|
||||
target: https://api.buildkite.com/v2/
|
||||
headers:
|
||||
Authorization: ${BUILDKITE_TOKEN}
|
||||
|
||||
'/sentry/api':
|
||||
target: https://sentry.io/api/
|
||||
allowedMethods: ['GET']
|
||||
headers:
|
||||
Authorization: ${SENTRY_TOKEN}
|
||||
|
||||
'/ilert':
|
||||
target: https://api.ilert.com
|
||||
allowedMethods: ['GET', 'POST', 'PUT']
|
||||
allowedHeaders: ['Authorization']
|
||||
headers:
|
||||
Authorization: ${ILERT_AUTH_HEADER}
|
||||
|
||||
'/airflow':
|
||||
target: https://your.airflow.instance.com/api/v1
|
||||
headers:
|
||||
Authorization: ${AIRFLOW_BASIC_AUTH_HEADER}
|
||||
|
||||
'/gocd':
|
||||
target: https://your.gocd.instance.com/go/api
|
||||
allowedMethods: ['GET']
|
||||
allowedHeaders: ['Authorization']
|
||||
headers:
|
||||
Authorization: Basic ${GOCD_AUTH_CREDENTIALS}
|
||||
|
||||
'/dynatrace':
|
||||
target: https://your.dynatrace.instance.com/api/v2
|
||||
headers:
|
||||
Authorization: 'Api-Token ${DYNATRACE_ACCESS_TOKEN}'
|
||||
|
||||
techdocs:
|
||||
builder: 'local' # Alternatives - 'external'
|
||||
generator:
|
||||
runIn: 'docker'
|
||||
# dockerImage: my-org/techdocs # use a custom docker image
|
||||
# pullImage: true # or false to disable automatic pulling of image (e.g. if custom docker login is required)
|
||||
publisher:
|
||||
type: 'local' # Alternatives - 'googleGcs' or 'awsS3' or 'azureBlobStorage' or 'openStackSwift'. Read documentation for using alternatives.
|
||||
|
||||
dynatrace:
|
||||
baseUrl: https://your.dynatrace.instance.com
|
||||
|
||||
# Score-cards sample configuration.
|
||||
scorecards:
|
||||
jsonDataUrl: https://raw.githubusercontent.com/Oriflame/backstage-plugins/main/plugins/score-card/sample-data/
|
||||
wikiLinkTemplate: https://link-to-wiki/{id}
|
||||
|
||||
sentry:
|
||||
organization: my-company
|
||||
|
||||
rollbar:
|
||||
organization: my-company
|
||||
# NOTE: The rollbar-backend & accountToken key may be deprecated in the future (replaced by a proxy config)
|
||||
accountToken: my-rollbar-account-token
|
||||
|
||||
lighthouse:
|
||||
baseUrl: http://localhost:3003
|
||||
|
||||
kubernetes:
|
||||
serviceLocatorMethod:
|
||||
type: 'multiTenant'
|
||||
clusterLocatorMethods:
|
||||
- type: 'config'
|
||||
clusters: []
|
||||
|
||||
kafka:
|
||||
clientId: backstage
|
||||
clusters:
|
||||
- name: cluster
|
||||
dashboardUrl: https://akhq.io/
|
||||
brokers:
|
||||
- localhost:9092
|
||||
|
||||
allure:
|
||||
baseUrl: http://localhost:5050/allure-docker-service
|
||||
|
||||
integrations:
|
||||
github:
|
||||
- host: github.com
|
||||
token: ${GITHUB_TOKEN}
|
||||
### Example for how to add your GitHub Enterprise instance using the API:
|
||||
# - host: ghe.example.net
|
||||
# apiBaseUrl: https://ghe.example.net/api/v3
|
||||
# token: ${GHE_TOKEN}
|
||||
### Example for how to add your GitHub Enterprise instance using raw HTTP fetches (token is optional):
|
||||
# - host: ghe.example.net
|
||||
# rawBaseUrl: https://ghe.example.net/raw
|
||||
# token: ${GHE_TOKEN}
|
||||
gitlab:
|
||||
- host: gitlab.com
|
||||
token: ${GITLAB_TOKEN}
|
||||
### Example for how to add a bitbucket cloud integration
|
||||
# bitbucketCloud:
|
||||
# - username: ${BITBUCKET_USERNAME}
|
||||
# appPassword: ${BITBUCKET_APP_PASSWORD}
|
||||
### Example for how to add your bitbucket server instance using the API:
|
||||
# - host: server.bitbucket.com
|
||||
# apiBaseUrl: server.bitbucket.com
|
||||
# username: ${BITBUCKET_SERVER_USERNAME}
|
||||
# appPassword: ${BITBUCKET_SERVER_APP_PASSWORD}
|
||||
azure:
|
||||
- host: dev.azure.com
|
||||
token: ${AZURE_TOKEN}
|
||||
# googleGcs:
|
||||
# clientEmail: 'example@example.com'
|
||||
# privateKey: ${GCS_PRIVATE_KEY}
|
||||
awsS3:
|
||||
- host: amazonaws.com
|
||||
accessKeyId: ${AWS_ACCESS_KEY_ID}
|
||||
secretAccessKey: ${AWS_SECRET_ACCESS_KEY}
|
||||
|
||||
costInsights:
|
||||
engineerCost: 200000
|
||||
engineerThreshold: 0.5
|
||||
products:
|
||||
computeEngine:
|
||||
name: Compute Engine
|
||||
icon: compute
|
||||
cloudDataflow:
|
||||
name: Cloud Dataflow
|
||||
icon: data
|
||||
cloudStorage:
|
||||
name: Cloud Storage
|
||||
icon: storage
|
||||
bigQuery:
|
||||
name: BigQuery
|
||||
icon: search
|
||||
events:
|
||||
name: Events
|
||||
icon: data
|
||||
metrics:
|
||||
DAU:
|
||||
name: Daily Active Users
|
||||
default: true
|
||||
MSC:
|
||||
name: Monthly Subscribers
|
||||
currencies:
|
||||
engineers:
|
||||
label: 'Engineers 🛠'
|
||||
unit: 'engineer'
|
||||
usd:
|
||||
label: 'US Dollars 💵'
|
||||
kind: 'USD'
|
||||
unit: 'dollar'
|
||||
prefix: '$'
|
||||
rate: 1
|
||||
carbonOffsetTons:
|
||||
label: 'Carbon Offset Tons ♻️⚖️s'
|
||||
kind: 'CARBON_OFFSET_TONS'
|
||||
unit: 'carbon offset ton'
|
||||
rate: 3.5
|
||||
beers:
|
||||
label: 'Beers 🍺'
|
||||
kind: 'BEERS'
|
||||
unit: 'beer'
|
||||
rate: 4.5
|
||||
pintsIceCream:
|
||||
label: 'Pints of Ice Cream 🍦'
|
||||
kind: 'PINTS_OF_ICE_CREAM'
|
||||
unit: 'ice cream pint'
|
||||
rate: 5.5
|
||||
pagerduty:
|
||||
eventsBaseUrl: 'https://events.pagerduty.com/v2'
|
||||
jenkins:
|
||||
instances:
|
||||
- name: default
|
||||
baseUrl: https://jenkins.example.com
|
||||
username: backstage-bot
|
||||
apiKey: 123456789abcdef0123456789abcedf012
|
||||
|
||||
azureDevOps:
|
||||
host: dev.azure.com
|
||||
token: my-token
|
||||
organization: my-company
|
||||
|
||||
apacheAirflow:
|
||||
baseUrl: https://your.airflow.instance.com
|
||||
|
||||
gocd:
|
||||
baseUrl: https://your.gocd.instance.com
|
||||
|
||||
permission:
|
||||
enabled: true
|
||||
|
||||
@@ -185,21 +185,10 @@ jobs:
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
- name: check for yarn.lock changes
|
||||
id: yarn-lock
|
||||
run: git diff --quiet origin/master HEAD -- yarn.lock
|
||||
continue-on-error: true
|
||||
|
||||
- name: lint changed packages
|
||||
if: ${{ steps.yarn-lock.outcome == 'success' }}
|
||||
run: yarn backstage-cli repo lint --since origin/master
|
||||
|
||||
- name: lint all packages
|
||||
if: ${{ steps.yarn-lock.outcome == 'failure' }}
|
||||
run: yarn backstage-cli repo lint
|
||||
|
||||
- name: test changed packages
|
||||
if: ${{ steps.yarn-lock.outcome == 'success' }}
|
||||
run: yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=1300M --since origin/master
|
||||
env:
|
||||
BACKSTAGE_TEST_DISABLE_DOCKER: 1
|
||||
@@ -207,17 +196,6 @@ jobs:
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres9.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored
|
||||
|
||||
- name: test all packages (and upload coverage)
|
||||
if: ${{ steps.yarn-lock.outcome == 'failure' }}
|
||||
run: |
|
||||
yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=800M --coverage
|
||||
bash <(curl -s https://codecov.io/bash) -N $(git rev-parse FETCH_HEAD)
|
||||
env:
|
||||
BACKSTAGE_TEST_DISABLE_DOCKER: 1
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres13.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres9.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored
|
||||
|
||||
- name: ensure clean working directory
|
||||
run: |
|
||||
if files=$(git ls-files --exclude-standard --others --modified) && [[ -z "$files" ]]; then
|
||||
|
||||
@@ -8,6 +8,9 @@ on:
|
||||
|
||||
jobs:
|
||||
build-backstage:
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
UFFIZZI_URL: https://uffizzi.com
|
||||
name: Build PR image
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }}
|
||||
@@ -16,8 +19,6 @@ jobs:
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: backstage
|
||||
|
||||
- name: setup-node
|
||||
uses: actions/setup-node@v3
|
||||
@@ -25,21 +26,22 @@ jobs:
|
||||
node-version: 16.x
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: backstage create-app | delete if already exists
|
||||
run: |
|
||||
rm -rf ./example-app
|
||||
npx @backstage/create-app
|
||||
env:
|
||||
BACKSTAGE_APP_NAME: example-app
|
||||
|
||||
- name: yarn build
|
||||
run: |
|
||||
yarn build:all
|
||||
working-directory: ./example-app
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.12
|
||||
with:
|
||||
cache-prefix: linux-v16
|
||||
|
||||
- name: Use Uffizzi's backstage app config
|
||||
run: |
|
||||
cp ./backstage/.github/uffizzi/uffizzi.production.app-config.yaml ./example-app/app-config.production.yaml;
|
||||
cp -f ./.github/uffizzi/uffizzi.production.app-config.yaml ./app-config.yaml
|
||||
|
||||
- name: typescript build
|
||||
run: |
|
||||
yarn tsc
|
||||
|
||||
- name: backstage build
|
||||
run: |
|
||||
yarn workspace example-backend build
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
@@ -58,8 +60,8 @@ jobs:
|
||||
- name: Build Image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: example-app
|
||||
file: example-app/packages/backend/Dockerfile
|
||||
context: .
|
||||
file: packages/backend/Dockerfile
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
push: true
|
||||
|
||||
@@ -251,6 +251,23 @@ the entity belongs to the `"default"` namespace.
|
||||
Namespaces may also be part of the catalog, and are `v1` / `Namespace` entities,
|
||||
i.e. not Backstage specific but the same as in Kubernetes.
|
||||
|
||||
### `uid` [output]
|
||||
|
||||
Each entity gets an automatically generated globally unique ID when it first
|
||||
enters the database. This field is not meant to be specified as input data, but
|
||||
is rater created by the database engine itself when producing the output entity.
|
||||
|
||||
Note that `uid` values are _not_ to be seen as stable, and should _not_ be used
|
||||
as external references to an entity. The `uid` can change over time even when a
|
||||
human observer might think that it wouldn't. As one of many examples,
|
||||
unregistering and re-registering the exact same file will result in a different
|
||||
`uid` value even though everything else is the same. Therefore there is very
|
||||
little, if any, reason to read or use this field externally.
|
||||
|
||||
If you want to refer to an entity by some form of an identifier, you should
|
||||
always use [string-form entity reference](references.md#string-references)
|
||||
instead.
|
||||
|
||||
### `title` [optional]
|
||||
|
||||
A display name of the entity, to be presented in user interfaces instead of the
|
||||
|
||||
@@ -44,8 +44,8 @@ spec:
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
catalogFilter:
|
||||
kind: Group
|
||||
- title: Choose a location
|
||||
required:
|
||||
- repoUrl
|
||||
@@ -165,11 +165,11 @@ this:
|
||||
"firstName": {
|
||||
"ui:autofocus": true,
|
||||
"ui:emptyValue": "",
|
||||
"ui:autocomplete": "family-name"
|
||||
"ui:autocomplete": "given-name"
|
||||
},
|
||||
"lastName": {
|
||||
"ui:emptyValue": "",
|
||||
"ui:autocomplete": "given-name"
|
||||
"ui:autocomplete": "family-name"
|
||||
},
|
||||
"nicknames": {
|
||||
"ui:options":{
|
||||
@@ -211,12 +211,12 @@ spec:
|
||||
default: Chuck
|
||||
ui:autofocus: true
|
||||
ui:emptyValue: ''
|
||||
ui:autocomplete: family-name
|
||||
ui:autocomplete: given-name
|
||||
lastName:
|
||||
type: string
|
||||
title: Last name
|
||||
ui:emptyValue: ''
|
||||
ui:autocomplete: given-name
|
||||
ui:autocomplete: family-name
|
||||
nicknames:
|
||||
type: array
|
||||
items:
|
||||
@@ -459,7 +459,7 @@ an owner for them. Ideally, users should be able to select an owner when they go
|
||||
through the scaffolder form from the users and groups already known to
|
||||
Backstage. The `OwnerPicker` is a custom field that generates a searchable list
|
||||
of groups and/or users already in the catalog to pick an owner from. You can
|
||||
specify which of the two kinds are listed in the `allowedKinds` option:
|
||||
specify which of the two kinds (or both) are listed in the `catalogFilter.kind` option:
|
||||
|
||||
```yaml
|
||||
owner:
|
||||
@@ -468,8 +468,8 @@ owner:
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
catalogFilter:
|
||||
kind: [Group, User]
|
||||
```
|
||||
|
||||
## `spec.steps` - `Action[]`
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"examples": "docusaurus-examples",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"start": "node scripts/pre-build.js && docusaurus start",
|
||||
"build": "node scripts/pre-build.js && docusaurus build",
|
||||
"prettier:check": "prettier --check .",
|
||||
"publish-gh-pages": "docusaurus-publish",
|
||||
"write-translations": "docusaurus-write-translations",
|
||||
@@ -20,7 +19,8 @@
|
||||
"devDependencies": {
|
||||
"@spotify/prettier-config": "^14.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "^2.6.2"
|
||||
"prettier": "^2.6.2",
|
||||
"replace": "^1.2.2"
|
||||
},
|
||||
"prettier": "@spotify/prettier-config",
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const replace = require('replace');
|
||||
const { existsSync, writeFileSync, mkdirSync } = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const PLACEHOLDER = `---
|
||||
id: "index"
|
||||
title: "Package Index"
|
||||
description: "Index of all Backstage Packages"
|
||||
---
|
||||
Run \`yarn build:api-docs\` to generate the API docs.
|
||||
`;
|
||||
|
||||
async function main() {
|
||||
const referencesDir = '../docs/reference';
|
||||
if (existsSync(referencesDir)) {
|
||||
console.log('Removing HTML comments from docs/reference folder');
|
||||
replace({
|
||||
regex: '<!--(.*?)-->',
|
||||
replacement: '',
|
||||
paths: [referencesDir],
|
||||
recursive: true,
|
||||
silent: false,
|
||||
});
|
||||
} else {
|
||||
mkdirSync(referencesDir);
|
||||
writeFileSync(path.join(referencesDir, 'index.md'), PLACEHOLDER);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1 +1,366 @@
|
||||
{}
|
||||
{
|
||||
"releases": {
|
||||
"Release Notes": [
|
||||
"releases/v1.8.0",
|
||||
"releases/v1.7.0",
|
||||
"releases/v1.6.0",
|
||||
"releases/v1.5.0",
|
||||
"releases/v1.4.0",
|
||||
"releases/v1.3.0",
|
||||
"releases/v1.2.0",
|
||||
"releases/v1.1.0",
|
||||
"releases/v1.0.0"
|
||||
]
|
||||
},
|
||||
"docs": {
|
||||
"Overview": [
|
||||
"overview/what-is-backstage",
|
||||
"overview/architecture-overview",
|
||||
"overview/roadmap",
|
||||
"overview/vision",
|
||||
"overview/background",
|
||||
"overview/adopting",
|
||||
"overview/versioning-policy",
|
||||
"overview/threat-model",
|
||||
"overview/support",
|
||||
"overview/glossary",
|
||||
"overview/logos"
|
||||
],
|
||||
"Getting Started": [
|
||||
"getting-started/index",
|
||||
"getting-started/configuration",
|
||||
"getting-started/create-an-app",
|
||||
"getting-started/running-backstage-locally",
|
||||
{
|
||||
"type": "category",
|
||||
"label": "App configuration",
|
||||
"items": [
|
||||
"getting-started/configure-app-with-plugins",
|
||||
"getting-started/app-custom-theme",
|
||||
"getting-started/homepage"
|
||||
]
|
||||
},
|
||||
"getting-started/keeping-backstage-updated",
|
||||
"getting-started/concepts",
|
||||
"getting-started/contributors",
|
||||
"getting-started/project-structure"
|
||||
],
|
||||
"Local Development": [
|
||||
{
|
||||
"type": "category",
|
||||
"label": "CLI",
|
||||
"items": [
|
||||
"local-dev/cli-overview",
|
||||
"local-dev/cli-build-system",
|
||||
"local-dev/cli-commands"
|
||||
]
|
||||
},
|
||||
"local-dev/linking-local-packages"
|
||||
],
|
||||
"Core Features": [
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Software Catalog",
|
||||
"items": [
|
||||
"features/software-catalog/software-catalog-overview",
|
||||
"features/software-catalog/life-of-an-entity",
|
||||
"features/software-catalog/configuration",
|
||||
"features/software-catalog/system-model",
|
||||
"features/software-catalog/descriptor-format",
|
||||
"features/software-catalog/references",
|
||||
"features/software-catalog/well-known-annotations",
|
||||
"features/software-catalog/well-known-relations",
|
||||
"features/software-catalog/well-known-statuses",
|
||||
"features/software-catalog/extending-the-model",
|
||||
"features/software-catalog/external-integrations",
|
||||
"features/software-catalog/catalog-customization",
|
||||
"features/software-catalog/software-catalog-api"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Kubernetes",
|
||||
"items": [
|
||||
"features/kubernetes/overview",
|
||||
"features/kubernetes/installation",
|
||||
"features/kubernetes/configuration",
|
||||
"features/kubernetes/authentication",
|
||||
"features/kubernetes/troubleshooting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Software Templates",
|
||||
"items": [
|
||||
"features/software-templates/software-templates-index",
|
||||
"features/software-templates/configuration",
|
||||
"features/software-templates/adding-templates",
|
||||
"features/software-templates/writing-templates",
|
||||
"features/software-templates/input-examples",
|
||||
"features/software-templates/builtin-actions",
|
||||
"features/software-templates/writing-custom-actions",
|
||||
"features/software-templates/writing-custom-field-extensions",
|
||||
"features/software-templates/writing-custom-step-layouts",
|
||||
"features/software-templates/migrating-from-v1beta2-to-v1beta3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Backstage Search",
|
||||
"items": [
|
||||
"features/search/search-overview",
|
||||
"features/search/getting-started",
|
||||
"features/search/concepts",
|
||||
"features/search/architecture",
|
||||
"features/search/search-engines",
|
||||
"features/search/how-to-guides"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "TechDocs",
|
||||
"items": [
|
||||
"features/techdocs/techdocs-overview",
|
||||
"features/techdocs/getting-started",
|
||||
"features/techdocs/concepts",
|
||||
"features/techdocs/addons",
|
||||
"features/techdocs/architecture",
|
||||
"features/techdocs/creating-and-publishing",
|
||||
"features/techdocs/configuration",
|
||||
"features/techdocs/using-cloud-storage",
|
||||
"features/techdocs/configuring-ci-cd",
|
||||
"features/techdocs/cli",
|
||||
"features/techdocs/how-to-guides",
|
||||
"features/techdocs/troubleshooting",
|
||||
"features/techdocs/faqs"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Integrations": [
|
||||
"integrations/index",
|
||||
{
|
||||
"type": "category",
|
||||
"label": "AWS S3",
|
||||
"items": [
|
||||
"integrations/aws-s3/locations",
|
||||
"integrations/aws-s3/discovery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Azure",
|
||||
"items": [
|
||||
"integrations/azure/locations",
|
||||
"integrations/azure/discovery",
|
||||
"integrations/azure/org"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Bitbucket Cloud",
|
||||
"items": [
|
||||
"integrations/bitbucketCloud/locations",
|
||||
"integrations/bitbucketCloud/discovery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Bitbucket Server",
|
||||
"items": [
|
||||
"integrations/bitbucketServer/locations",
|
||||
"integrations/bitbucketServer/discovery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Datadog",
|
||||
"items": ["integrations/datadog-rum/installation"]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Gerrit",
|
||||
"items": [
|
||||
"integrations/gerrit/locations",
|
||||
"integrations/gerrit/discovery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "GitHub",
|
||||
"items": [
|
||||
"integrations/github/locations",
|
||||
"integrations/github/discovery",
|
||||
"integrations/github/org",
|
||||
"integrations/github/github-apps"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "GitLab",
|
||||
"items": [
|
||||
"integrations/gitlab/locations",
|
||||
"integrations/gitlab/discovery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Gitea",
|
||||
"items": ["integrations/gitea/locations"]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Google GCS",
|
||||
"items": ["integrations/google-cloud-storage/locations"]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "LDAP",
|
||||
"items": ["integrations/ldap/org"]
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
"plugins/index",
|
||||
"plugins/existing-plugins",
|
||||
"plugins/create-a-plugin",
|
||||
"plugins/plugin-development",
|
||||
"plugins/structure-of-a-plugin",
|
||||
"plugins/integrating-plugin-into-software-catalog",
|
||||
"plugins/integrating-search-into-plugins",
|
||||
"plugins/composability",
|
||||
"plugins/customization",
|
||||
"plugins/analytics",
|
||||
"plugins/feature-flags",
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Backends and APIs",
|
||||
"items": [
|
||||
"plugins/proxying",
|
||||
"plugins/backend-plugin",
|
||||
"plugins/call-existing-api",
|
||||
"plugins/url-reader",
|
||||
"plugins/new-backend-system"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Testing",
|
||||
"items": ["plugins/testing"]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Publishing",
|
||||
"items": [
|
||||
"plugins/publish-private",
|
||||
"plugins/add-to-marketplace",
|
||||
"plugins/observability"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Configuration": [
|
||||
"conf/index",
|
||||
"conf/reading",
|
||||
"conf/writing",
|
||||
"conf/defining"
|
||||
],
|
||||
"Auth and identity": [
|
||||
"auth/index",
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Included providers",
|
||||
"items": [
|
||||
"auth/auth0/provider",
|
||||
"auth/atlassian/provider",
|
||||
"auth/bitbucket/provider",
|
||||
"auth/microsoft/provider",
|
||||
"auth/github/provider",
|
||||
"auth/gitlab/provider",
|
||||
"auth/google/provider",
|
||||
"auth/google/gcp-iap-auth",
|
||||
"auth/okta/provider",
|
||||
"auth/onelogin/provider",
|
||||
"auth/oauth2-proxy/provider"
|
||||
]
|
||||
},
|
||||
"auth/identity-resolver",
|
||||
"auth/oauth",
|
||||
"auth/oidc",
|
||||
"auth/add-auth-provider",
|
||||
"auth/service-to-service-auth",
|
||||
"auth/troubleshooting",
|
||||
"auth/glossary"
|
||||
],
|
||||
"Permissions": [
|
||||
"permissions/overview",
|
||||
"permissions/concepts",
|
||||
"permissions/getting-started",
|
||||
"permissions/writing-a-policy",
|
||||
"permissions/frontend-integration",
|
||||
"permissions/custom-rules",
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Tutorial: using Permissions in your plugin",
|
||||
"items": [
|
||||
"permissions/plugin-authors/01-setup",
|
||||
"permissions/plugin-authors/02-adding-a-basic-permission-check",
|
||||
"permissions/plugin-authors/03-adding-a-resource-permission-check",
|
||||
"permissions/plugin-authors/04-authorizing-access-to-paginated-data",
|
||||
"permissions/plugin-authors/05-frontend-authorization"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Deployment": [
|
||||
"deployment/index",
|
||||
"deployment/scaling",
|
||||
"deployment/docker",
|
||||
"deployment/k8s",
|
||||
"deployment/heroku"
|
||||
],
|
||||
"Designing for Backstage": [
|
||||
"dls/design",
|
||||
"dls/component-design-guidelines",
|
||||
"dls/contributing-to-storybook",
|
||||
"dls/figma"
|
||||
],
|
||||
"API Reference": [
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Guides",
|
||||
"items": ["api/utility-apis"]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "API Reference",
|
||||
"items": ["reference/index"]
|
||||
},
|
||||
"api/deprecations"
|
||||
],
|
||||
"Tutorials": [
|
||||
"tutorials/journey",
|
||||
"tutorials/quickstart-app-plugin",
|
||||
"tutorials/react-router-stable-migration",
|
||||
"tutorials/package-role-migration",
|
||||
"tutorials/migrating-away-from-core",
|
||||
"tutorials/configuring-plugin-databases",
|
||||
"tutorials/switching-sqlite-postgres",
|
||||
"tutorials/using-backstage-proxy-within-plugin",
|
||||
"tutorials/yarn-migration"
|
||||
],
|
||||
"Architecture Decision Records (ADRs)": [
|
||||
"architecture-decisions/adrs-overview",
|
||||
"architecture-decisions/adrs-adr001",
|
||||
"architecture-decisions/adrs-adr002",
|
||||
"architecture-decisions/adrs-adr003",
|
||||
"architecture-decisions/adrs-adr004",
|
||||
"architecture-decisions/adrs-adr005",
|
||||
"architecture-decisions/adrs-adr006",
|
||||
"architecture-decisions/adrs-adr007",
|
||||
"architecture-decisions/adrs-adr008",
|
||||
"architecture-decisions/adrs-adr009",
|
||||
"architecture-decisions/adrs-adr010",
|
||||
"architecture-decisions/adrs-adr011",
|
||||
"architecture-decisions/adrs-adr012",
|
||||
"architecture-decisions/adrs-adr013"
|
||||
],
|
||||
"FAQ": ["FAQ"]
|
||||
}
|
||||
}
|
||||
|
||||
+126
-2
@@ -3573,6 +3573,7 @@ __metadata:
|
||||
prettier: ^2.6.2
|
||||
react: ^17.0.2
|
||||
react-dom: ^17.0.2
|
||||
replace: ^1.2.2
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -3827,6 +3828,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"camelcase@npm:^5.0.0":
|
||||
version: 5.3.1
|
||||
resolution: "camelcase@npm:5.3.1"
|
||||
checksum: e6effce26b9404e3c0f301498184f243811c30dfe6d0b9051863bd8e4034d09c8c2923794f280d6827e5aa055f6c434115ff97864a16a963366fb35fd673024b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"camelcase@npm:^6.2.0":
|
||||
version: 6.3.0
|
||||
resolution: "camelcase@npm:6.3.0"
|
||||
@@ -3860,7 +3868,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:^2.0.0":
|
||||
"chalk@npm:2.4.2, chalk@npm:^2.0.0":
|
||||
version: 2.4.2
|
||||
resolution: "chalk@npm:2.4.2"
|
||||
dependencies:
|
||||
@@ -4021,6 +4029,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cliui@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "cliui@npm:6.0.0"
|
||||
dependencies:
|
||||
string-width: ^4.2.0
|
||||
strip-ansi: ^6.0.0
|
||||
wrap-ansi: ^6.2.0
|
||||
checksum: 4fcfd26d292c9f00238117f39fc797608292ae36bac2168cfee4c85923817d0607fe21b3329a8621e01aedf512c99b7eaa60e363a671ffd378df6649fb48ae42
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"clone-deep@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "clone-deep@npm:4.0.1"
|
||||
@@ -4597,6 +4616,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decamelize@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "decamelize@npm:1.2.0"
|
||||
checksum: ad8c51a7e7e0720c70ec2eeb1163b66da03e7616d7b98c9ef43cce2416395e84c1e9548dd94f5f6ffecfee9f8b94251fc57121a8b021f2ff2469b2bae247b8aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decompress-response@npm:^3.3.0":
|
||||
version: 3.3.0
|
||||
resolution: "decompress-response@npm:3.3.0"
|
||||
@@ -5355,7 +5381,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"find-up@npm:^4.0.0":
|
||||
"find-up@npm:^4.0.0, find-up@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "find-up@npm:4.1.0"
|
||||
dependencies:
|
||||
@@ -5544,6 +5570,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-caller-file@npm:^2.0.1":
|
||||
version: 2.0.5
|
||||
resolution: "get-caller-file@npm:2.0.5"
|
||||
checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1":
|
||||
version: 1.1.3
|
||||
resolution: "get-intrinsic@npm:1.1.3"
|
||||
@@ -7164,6 +7197,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:3.0.5":
|
||||
version: 3.0.5
|
||||
resolution: "minimatch@npm:3.0.5"
|
||||
dependencies:
|
||||
brace-expansion: ^1.1.7
|
||||
checksum: a3b84b426eafca947741b864502cee02860c4e7b145de11ad98775cfcf3066fef422583bc0ffce0952ddf4750c1ccf4220b1556430d4ce10139f66247d87d69e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:3.1.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1":
|
||||
version: 3.1.2
|
||||
resolution: "minimatch@npm:3.1.2"
|
||||
@@ -8982,6 +9024,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"replace@npm:^1.2.2":
|
||||
version: 1.2.2
|
||||
resolution: "replace@npm:1.2.2"
|
||||
dependencies:
|
||||
chalk: 2.4.2
|
||||
minimatch: 3.0.5
|
||||
yargs: ^15.3.1
|
||||
bin:
|
||||
replace: bin/replace.js
|
||||
search: bin/search.js
|
||||
checksum: 1d69f43937a5fdf9dea278e78d6f3b51c1889ba5135bd201918bbda6330684adf8276e8e90e1c021034420dd4df239e51c23ca40752cb9bc6180c153d6d46a37
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"require-directory@npm:^2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "require-directory@npm:2.1.1"
|
||||
checksum: fb47e70bf0001fdeabdc0429d431863e9475e7e43ea5f94ad86503d918423c1543361cc5166d713eaa7029dd7a3d34775af04764bebff99ef413111a5af18c80
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"require-from-string@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "require-from-string@npm:2.0.2"
|
||||
@@ -8996,6 +9059,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"require-main-filename@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "require-main-filename@npm:2.0.0"
|
||||
checksum: e9e294695fea08b076457e9ddff854e81bffbe248ed34c1eec348b7abbd22a0d02e8d75506559e2265e96978f3c4720bd77a6dad84755de8162b357eb6c778c7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"requires-port@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "requires-port@npm:1.0.0"
|
||||
@@ -10623,6 +10693,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which-module@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "which-module@npm:2.0.0"
|
||||
checksum: 809f7fd3dfcb2cdbe0180b60d68100c88785084f8f9492b0998c051d7a8efe56784492609d3f09ac161635b78ea29219eb1418a98c15ce87d085bce905705c9c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which@npm:^1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "which@npm:1.3.1"
|
||||
@@ -10679,6 +10756,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wrap-ansi@npm:^6.2.0":
|
||||
version: 6.2.0
|
||||
resolution: "wrap-ansi@npm:6.2.0"
|
||||
dependencies:
|
||||
ansi-styles: ^4.0.0
|
||||
string-width: ^4.1.0
|
||||
strip-ansi: ^6.0.0
|
||||
checksum: 6cd96a410161ff617b63581a08376f0cb9162375adeb7956e10c8cd397821f7eb2a6de24eb22a0b28401300bf228c86e50617cd568209b5f6775b93c97d2fe3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wrap-ansi@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "wrap-ansi@npm:7.0.0"
|
||||
@@ -10775,6 +10863,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"y18n@npm:^4.0.0":
|
||||
version: 4.0.3
|
||||
resolution: "y18n@npm:4.0.3"
|
||||
checksum: 014dfcd9b5f4105c3bb397c1c8c6429a9df004aa560964fb36732bfb999bfe83d45ae40aeda5b55d21b1ee53d8291580a32a756a443e064317953f08025b1aa4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yallist@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "yallist@npm:4.0.0"
|
||||
@@ -10789,6 +10884,35 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs-parser@npm:^18.1.2":
|
||||
version: 18.1.3
|
||||
resolution: "yargs-parser@npm:18.1.3"
|
||||
dependencies:
|
||||
camelcase: ^5.0.0
|
||||
decamelize: ^1.2.0
|
||||
checksum: 60e8c7d1b85814594d3719300ecad4e6ae3796748b0926137bfec1f3042581b8646d67e83c6fc80a692ef08b8390f21ddcacb9464476c39bbdf52e34961dd4d9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs@npm:^15.3.1":
|
||||
version: 15.4.1
|
||||
resolution: "yargs@npm:15.4.1"
|
||||
dependencies:
|
||||
cliui: ^6.0.0
|
||||
decamelize: ^1.2.0
|
||||
find-up: ^4.1.0
|
||||
get-caller-file: ^2.0.1
|
||||
require-directory: ^2.1.1
|
||||
require-main-filename: ^2.0.0
|
||||
set-blocking: ^2.0.0
|
||||
string-width: ^4.2.0
|
||||
which-module: ^2.0.0
|
||||
y18n: ^4.0.0
|
||||
yargs-parser: ^18.1.2
|
||||
checksum: 40b974f508d8aed28598087720e086ecd32a5fd3e945e95ea4457da04ee9bdb8bdd17fd91acff36dc5b7f0595a735929c514c40c402416bbb87c03f6fb782373
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yocto-queue@npm:^0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "yocto-queue@npm:0.1.0"
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@
|
||||
"techdocs-cli": "node scripts/techdocs-cli.js",
|
||||
"techdocs-cli:dev": "cross-env TECHDOCS_CLI_DEV_MODE=true node scripts/techdocs-cli.js",
|
||||
"prepare": "husky install",
|
||||
"postinstall": "husky install"
|
||||
"postinstall": "husky install || true"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
|
||||
@@ -31,8 +31,8 @@ parameters:
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
catalogFilter:
|
||||
kind: Group
|
||||
- title: Choose a location
|
||||
required:
|
||||
- repoUrl
|
||||
|
||||
@@ -84,6 +84,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise<void> {
|
||||
const graph = PackageGraph.fromPackages(packages);
|
||||
const changedPackages = await graph.listChangedPackages({
|
||||
ref: opts.since,
|
||||
analyzeLockfile: true,
|
||||
});
|
||||
const withDevDependents = graph.collectPackageNames(
|
||||
changedPackages.map(pkg => pkg.name),
|
||||
|
||||
@@ -34,7 +34,10 @@ export async function command(opts: OptionValues): Promise<void> {
|
||||
|
||||
if (opts.since) {
|
||||
const graph = PackageGraph.fromPackages(packages);
|
||||
packages = await graph.listChangedPackages({ ref: opts.since });
|
||||
packages = await graph.listChangedPackages({
|
||||
ref: opts.since,
|
||||
analyzeLockfile: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Packages are ordered from most to least number of dependencies, as a
|
||||
|
||||
@@ -93,6 +93,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise<void> {
|
||||
const graph = PackageGraph.fromPackages(packages);
|
||||
const changedPackages = await graph.listChangedPackages({
|
||||
ref: opts.since,
|
||||
analyzeLockfile: true,
|
||||
});
|
||||
|
||||
const packageNames = Array.from(
|
||||
|
||||
@@ -21,7 +21,8 @@ import { serveBundle } from '../../lib/bundler';
|
||||
import { loadCliConfig } from '../../lib/config';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { Lockfile } from '../../lib/versioning';
|
||||
import { includedFilter } from '../versions/lint';
|
||||
import { forbiddenDuplicatesFilter, includedFilter } from '../versions/lint';
|
||||
import { PackageGraph } from '../../lib/monorepo';
|
||||
|
||||
interface StartAppOptions {
|
||||
verifyVersions?: boolean;
|
||||
@@ -36,10 +37,13 @@ export async function startFrontend(options: StartAppOptions) {
|
||||
const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock'));
|
||||
const result = lockfile.analyze({
|
||||
filter: includedFilter,
|
||||
localPackages: PackageGraph.fromPackages(
|
||||
await PackageGraph.listTargetPackages(),
|
||||
),
|
||||
});
|
||||
const problemPackages = [...result.newVersions, ...result.newRanges].map(
|
||||
({ name }) => name,
|
||||
);
|
||||
const problemPackages = [...result.newVersions, ...result.newRanges]
|
||||
.map(({ name }) => name)
|
||||
.filter(forbiddenDuplicatesFilter);
|
||||
|
||||
if (problemPackages.length > 1) {
|
||||
console.log(
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
ReleaseManifest,
|
||||
} from '@backstage/release-manifests';
|
||||
import 'global-agent/bootstrap';
|
||||
import { PackageGraph } from '../../lib/monorepo';
|
||||
|
||||
const DEP_TYPES = [
|
||||
'dependencies',
|
||||
@@ -200,7 +201,7 @@ export default async (opts: OptionValues) => {
|
||||
lockfile.remove(name, range);
|
||||
}
|
||||
}
|
||||
await lockfile.save();
|
||||
await lockfile.save(lockfilePath);
|
||||
}
|
||||
|
||||
const breakingUpdates = new Map<string, { from: string; to: string }>();
|
||||
@@ -305,8 +306,12 @@ export default async (opts: OptionValues) => {
|
||||
|
||||
// Finally we make sure the new lockfile doesn't have any duplicates
|
||||
const dedupLockfile = await Lockfile.load(lockfilePath);
|
||||
|
||||
const result = dedupLockfile.analyze({
|
||||
filter,
|
||||
localPackages: PackageGraph.fromPackages(
|
||||
await PackageGraph.listTargetPackages(),
|
||||
),
|
||||
});
|
||||
|
||||
if (result.newVersions.length > 0) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import { OptionValues } from 'commander';
|
||||
import { Lockfile } from '../../lib/versioning';
|
||||
import { paths } from '../../lib/paths';
|
||||
import partition from 'lodash/partition';
|
||||
import { PackageGraph } from '../../lib/monorepo';
|
||||
|
||||
// Packages that we try to avoid duplicates for
|
||||
const INCLUDED = [/^@backstage\//];
|
||||
@@ -52,9 +53,13 @@ export default async (cmd: OptionValues) => {
|
||||
|
||||
let success = true;
|
||||
|
||||
const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock'));
|
||||
const lockfilePath = paths.resolveTargetRoot('yarn.lock');
|
||||
const lockfile = await Lockfile.load(lockfilePath);
|
||||
const result = lockfile.analyze({
|
||||
filter: includedFilter,
|
||||
localPackages: PackageGraph.fromPackages(
|
||||
await PackageGraph.listTargetPackages(),
|
||||
),
|
||||
});
|
||||
|
||||
logArray(
|
||||
@@ -65,7 +70,7 @@ export default async (cmd: OptionValues) => {
|
||||
|
||||
if (fix) {
|
||||
lockfile.replaceVersions(result.newVersions);
|
||||
await lockfile.save();
|
||||
await lockfile.save(lockfilePath);
|
||||
} else {
|
||||
const [newVersionsForbidden, newVersionsAllowed] = partition(
|
||||
result.newVersions,
|
||||
|
||||
@@ -64,3 +64,23 @@ export async function listChangedFiles(ref: string) {
|
||||
|
||||
return Array.from(new Set([...tracked, ...untracked]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of a file at a specific ref.
|
||||
*/
|
||||
export async function readFileAtRef(path: string, ref: string) {
|
||||
let showRef = ref;
|
||||
try {
|
||||
const [base] = await runGit('merge-base', 'HEAD', ref);
|
||||
showRef = base;
|
||||
} catch {
|
||||
// silently fall back to using the ref directly if merge base is not available
|
||||
}
|
||||
|
||||
const { stdout } = await execFile('git', ['show', `${showRef}:${path}`], {
|
||||
shell: true,
|
||||
cwd: paths.targetRoot,
|
||||
maxBuffer: 1024 * 1024 * 50,
|
||||
});
|
||||
return stdout;
|
||||
}
|
||||
|
||||
@@ -14,19 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { PackageGraph } from './PackageGraph';
|
||||
import { listChangedFiles } from '../git';
|
||||
import { Lockfile } from '../versioning/Lockfile';
|
||||
import { listChangedFiles, readFileAtRef } from '../git';
|
||||
|
||||
jest.mock('../git');
|
||||
|
||||
const mockListChangedFiles = listChangedFiles as jest.MockedFunction<
|
||||
typeof listChangedFiles
|
||||
>;
|
||||
const mockReadFileAtRef = readFileAtRef as jest.MockedFunction<
|
||||
typeof readFileAtRef
|
||||
>;
|
||||
|
||||
jest.mock('../paths', () => ({
|
||||
paths: {
|
||||
targetRoot: '/',
|
||||
resolveTargetRoot: (...paths: string[]) => resolvePath('/', ...paths),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -177,4 +183,54 @@ describe('PackageGraph', () => {
|
||||
graph.listChangedPackages({ ref: 'origin/master' }),
|
||||
).resolves.toEqual([graph.get('a'), graph.get('b')]);
|
||||
});
|
||||
|
||||
it('lists changed packages with lockfile analysis', async () => {
|
||||
const graph = PackageGraph.fromPackages(testPackages);
|
||||
|
||||
mockListChangedFiles.mockResolvedValueOnce(
|
||||
['README.md', 'packages/a/src/foo.ts', 'yarn.lock'].sort(),
|
||||
);
|
||||
mockReadFileAtRef.mockResolvedValueOnce(`
|
||||
a@^1:
|
||||
version: "1.0.0"
|
||||
|
||||
c@^1:
|
||||
version: "1.0.0"
|
||||
dependencies:
|
||||
c-dep: ^1
|
||||
|
||||
c-dep@^2:
|
||||
version: "2.0.0"
|
||||
integrity: sha512-xyz
|
||||
`);
|
||||
jest.spyOn(Lockfile, 'load').mockResolvedValueOnce(
|
||||
Lockfile.parse(`
|
||||
a@^1:
|
||||
version: "1.0.0"
|
||||
|
||||
c@^1:
|
||||
version: "1.0.0"
|
||||
dependencies:
|
||||
c-dep: ^1
|
||||
|
||||
c-dep@^2:
|
||||
version: "2.0.0"
|
||||
integrity: sha512-xyz-other
|
||||
`),
|
||||
);
|
||||
|
||||
await expect(
|
||||
graph
|
||||
.listChangedPackages({
|
||||
ref: 'origin/master',
|
||||
analyzeLockfile: true,
|
||||
})
|
||||
.then(pkgs => pkgs.map(pkg => pkg.name)),
|
||||
).resolves.toEqual(['a', 'c']);
|
||||
|
||||
expect(mockReadFileAtRef).toHaveBeenCalledWith(
|
||||
'yarn.lock',
|
||||
'origin/master',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,8 @@ import path from 'path';
|
||||
import { getPackages, Package } from '@manypkg/get-packages';
|
||||
import { paths } from '../paths';
|
||||
import { PackageRole } from '../role';
|
||||
import { listChangedFiles } from '../git';
|
||||
import { listChangedFiles, readFileAtRef } from '../git';
|
||||
import { Lockfile } from '../versioning';
|
||||
|
||||
type PackageJSON = Package['packageJson'];
|
||||
|
||||
@@ -191,7 +192,10 @@ export class PackageGraph extends Map<string, PackageGraphNode> {
|
||||
return targets;
|
||||
}
|
||||
|
||||
async listChangedPackages(options: { ref: string }) {
|
||||
async listChangedPackages(options: {
|
||||
ref: string;
|
||||
analyzeLockfile?: boolean;
|
||||
}) {
|
||||
const changedFiles = await listChangedFiles(options.ref);
|
||||
|
||||
const dirMap = new Map(
|
||||
@@ -234,6 +238,77 @@ export class PackageGraph extends Map<string, PackageGraphNode> {
|
||||
}
|
||||
}
|
||||
|
||||
if (changedFiles.includes('yarn.lock') && options.analyzeLockfile) {
|
||||
// Load the lockfile in the working tree and the one at the ref and diff them
|
||||
let thisLockfile: Lockfile;
|
||||
let otherLockfile: Lockfile;
|
||||
try {
|
||||
thisLockfile = await Lockfile.load(
|
||||
paths.resolveTargetRoot('yarn.lock'),
|
||||
);
|
||||
otherLockfile = Lockfile.parse(
|
||||
await readFileAtRef('yarn.lock', options.ref),
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Failed to read lockfiles, assuming all packages have changed, ${error}`,
|
||||
);
|
||||
return Array.from(this.values());
|
||||
}
|
||||
const diff = thisLockfile.diff(otherLockfile);
|
||||
|
||||
// Create a simplified dependency graph only keeps track of package names
|
||||
const graph = thisLockfile.createSimplifiedDependencyGraph();
|
||||
|
||||
// Merge the dependency graph from the other lockfile into this one in
|
||||
// order to be able to detect removals accurately.
|
||||
{
|
||||
const otherGraph = thisLockfile.createSimplifiedDependencyGraph();
|
||||
for (const [name, dependencies] of otherGraph) {
|
||||
const node = graph.get(name);
|
||||
if (node) {
|
||||
dependencies.forEach(d => node.add(d));
|
||||
} else {
|
||||
graph.set(name, dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The check is simplified by only considering the package names rather
|
||||
// than the exact version range queries that were changed.
|
||||
// TODO(Rugvip): Use a more exact check
|
||||
const changedPackages = new Set(
|
||||
[...diff.added, ...diff.changed, ...diff.removed].map(e => e.name),
|
||||
);
|
||||
|
||||
// Starting with our set of changed packages from the diff, we loop through
|
||||
// the full graph and add any package that has a dependency on a changed package.
|
||||
// We keep looping until all transitive dependencies have been detected.
|
||||
let changed = false;
|
||||
do {
|
||||
changed = false;
|
||||
for (const [name, dependencies] of graph) {
|
||||
if (changedPackages.has(name)) {
|
||||
continue;
|
||||
}
|
||||
for (const dep of dependencies) {
|
||||
if (changedPackages.has(dep)) {
|
||||
changed = true;
|
||||
changedPackages.add(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
|
||||
// Add all local packages that had a transitive dependency change to the result set
|
||||
for (const node of this.values()) {
|
||||
if (changedPackages.has(node.name) && !result.includes(node)) {
|
||||
result.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { resolve as resolvePath, join as joinPath } from 'path';
|
||||
import { paths } from '../../paths';
|
||||
import { Task } from '../../tasks';
|
||||
import { FactoryRegistry } from '../FactoryRegistry';
|
||||
@@ -73,7 +73,7 @@ describe('webLibraryPackage factory', () => {
|
||||
'',
|
||||
`Creating web-library package ${expectedwebLibraryPackageName}`,
|
||||
'Checking Prerequisites:',
|
||||
`availability packages/${expectedwebLibraryPackageName}`,
|
||||
`availability ${joinPath('packages', expectedwebLibraryPackageName)}`,
|
||||
'creating temp dir',
|
||||
'Executing Template:',
|
||||
'copying .eslintrc.js',
|
||||
@@ -82,7 +82,7 @@ describe('webLibraryPackage factory', () => {
|
||||
'templating index.ts.hbs',
|
||||
'copying setupTests.ts',
|
||||
'Installing:',
|
||||
`moving packages/${expectedwebLibraryPackageName}`,
|
||||
`moving ${joinPath('packages', expectedwebLibraryPackageName)}`,
|
||||
]);
|
||||
|
||||
await expect(
|
||||
|
||||
@@ -16,14 +16,23 @@
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import { ExtendedPackage } from '../monorepo';
|
||||
import { Lockfile } from './Lockfile';
|
||||
|
||||
const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
const LEGACY_HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
`;
|
||||
|
||||
const mockA = `${HEADER}
|
||||
const MODERN_HEADER = `# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 6
|
||||
cacheKey: 8
|
||||
`;
|
||||
|
||||
const mockA = `${LEGACY_HEADER}
|
||||
a@^1:
|
||||
version "1.0.1"
|
||||
resolved "https://my-registry/a-1.0.01.tgz#abc123"
|
||||
@@ -38,7 +47,7 @@ b@^2:
|
||||
version "2.0.0"
|
||||
`;
|
||||
|
||||
const mockADedup = `${HEADER}
|
||||
const mockADedup = `${LEGACY_HEADER}
|
||||
a@^1:
|
||||
version "1.0.1"
|
||||
resolved "https://my-registry/a-1.0.01.tgz#abc123"
|
||||
@@ -50,7 +59,7 @@ b@2.0.x, b@^2:
|
||||
version "2.0.1"
|
||||
`;
|
||||
|
||||
const mockB = `${HEADER}
|
||||
const mockB = `${LEGACY_HEADER}
|
||||
"@s/a@*", "@s/a@1 || 2", "@s/a@^1":
|
||||
version "1.0.1"
|
||||
|
||||
@@ -58,7 +67,7 @@ const mockB = `${HEADER}
|
||||
version "2.0.0"
|
||||
`;
|
||||
|
||||
const mockBDedup = `${HEADER}
|
||||
const mockBDedup = `${LEGACY_HEADER}
|
||||
"@s/a@*", "@s/a@1 || 2", "@s/a@^2.0.x":
|
||||
version "2.0.0"
|
||||
|
||||
@@ -77,10 +86,12 @@ describe('Lockfile', () => {
|
||||
});
|
||||
|
||||
const lockfile = await Lockfile.load('/yarn.lock');
|
||||
expect(lockfile.get('a')).toEqual([{ range: '^1', version: '1.0.1' }]);
|
||||
expect(lockfile.get('a')).toEqual([
|
||||
{ range: '^1', version: '1.0.1', dataKey: 'a@^1' },
|
||||
]);
|
||||
expect(lockfile.get('b')).toEqual([
|
||||
{ range: '2.0.x', version: '2.0.1' },
|
||||
{ range: '^2', version: '2.0.0' },
|
||||
{ range: '2.0.x', version: '2.0.1', dataKey: 'b@2.0.x' },
|
||||
{ range: '^2', version: '2.0.0', dataKey: 'b@^2' },
|
||||
]);
|
||||
expect(lockfile.toString()).toBe(mockA);
|
||||
});
|
||||
@@ -91,7 +102,7 @@ describe('Lockfile', () => {
|
||||
});
|
||||
|
||||
const lockfile = await Lockfile.load('/yarn.lock');
|
||||
const result = lockfile.analyze();
|
||||
const result = lockfile.analyze({ localPackages: new Map() });
|
||||
expect(result).toEqual({
|
||||
invalidRanges: [],
|
||||
newRanges: [],
|
||||
@@ -110,7 +121,7 @@ describe('Lockfile', () => {
|
||||
expect(lockfile.toString()).toBe(mockADedup);
|
||||
|
||||
await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(mockA);
|
||||
await expect(lockfile.save()).resolves.toBeUndefined();
|
||||
await expect(lockfile.save('/yarn.lock')).resolves.toBeUndefined();
|
||||
await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(mockADedup);
|
||||
});
|
||||
|
||||
@@ -120,7 +131,7 @@ describe('Lockfile', () => {
|
||||
});
|
||||
|
||||
const lockfile = await Lockfile.load('/yarn.lock');
|
||||
const result = lockfile.analyze();
|
||||
const result = lockfile.analyze({ localPackages: new Map() });
|
||||
expect(result).toEqual({
|
||||
invalidRanges: [],
|
||||
newRanges: [
|
||||
@@ -154,15 +165,7 @@ describe('Lockfile', () => {
|
||||
});
|
||||
});
|
||||
|
||||
const newHeader = `# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 6
|
||||
cacheKey: 8
|
||||
`;
|
||||
|
||||
const mockANew = `${newHeader}
|
||||
const mockANew = `${MODERN_HEADER}
|
||||
a@^1:
|
||||
version: 1.0.1
|
||||
dependencies:
|
||||
@@ -177,7 +180,7 @@ b@^2:
|
||||
version: 2.0.0
|
||||
`;
|
||||
|
||||
const mockANewDedup = `${newHeader}
|
||||
const mockANewDedup = `${MODERN_HEADER}
|
||||
a@^1:
|
||||
version: 1.0.1
|
||||
dependencies:
|
||||
@@ -192,6 +195,36 @@ b@^2:
|
||||
version: 2.0.1
|
||||
`;
|
||||
|
||||
const mockANewLocal = `${MODERN_HEADER}
|
||||
a@^1:
|
||||
version: 1.0.1
|
||||
dependencies:
|
||||
b: ^2
|
||||
integrity: sha512-xyz
|
||||
resolved: "https://my-registry/a-1.0.01.tgz#abc123"
|
||||
|
||||
"b@2.0.x, b@^2.0.1":
|
||||
version: 0.0.0-use.local
|
||||
|
||||
b@^2:
|
||||
version: 2.0.0
|
||||
`;
|
||||
|
||||
const mockANewLocalDedup = `${MODERN_HEADER}
|
||||
a@^1:
|
||||
version: 1.0.1
|
||||
dependencies:
|
||||
b: ^2
|
||||
integrity: sha512-xyz
|
||||
resolved: "https://my-registry/a-1.0.01.tgz#abc123"
|
||||
|
||||
"b@2.0.x, b@^2.0.1":
|
||||
version: 0.0.0-use.local
|
||||
|
||||
b@^2:
|
||||
version: 0.0.0-use.local
|
||||
`;
|
||||
|
||||
describe('New Lockfile', () => {
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
@@ -203,11 +236,13 @@ describe('New Lockfile', () => {
|
||||
});
|
||||
|
||||
const lockfile = await Lockfile.load('/yarn.lock');
|
||||
expect(lockfile.get('a')).toEqual([{ range: '^1', version: '1.0.1' }]);
|
||||
expect(lockfile.get('a')).toEqual([
|
||||
{ range: '^1', version: '1.0.1', dataKey: 'a@^1' },
|
||||
]);
|
||||
expect(lockfile.get('b')).toEqual([
|
||||
{ range: '2.0.x', version: '2.0.1' },
|
||||
{ range: '^2.0.1', version: '2.0.1' },
|
||||
{ range: '^2', version: '2.0.0' },
|
||||
{ range: '2.0.x', version: '2.0.1', dataKey: 'b@2.0.x, b@^2.0.1' },
|
||||
{ range: '^2.0.1', version: '2.0.1', dataKey: 'b@2.0.x, b@^2.0.1' },
|
||||
{ range: '^2', version: '2.0.0', dataKey: 'b@^2' },
|
||||
]);
|
||||
expect(lockfile.toString()).toBe(mockANew);
|
||||
});
|
||||
@@ -218,7 +253,7 @@ describe('New Lockfile', () => {
|
||||
});
|
||||
|
||||
const lockfile = await Lockfile.load('/yarn.lock');
|
||||
const result = lockfile.analyze();
|
||||
const result = lockfile.analyze({ localPackages: new Map() });
|
||||
expect(result).toEqual({
|
||||
invalidRanges: [],
|
||||
newRanges: [],
|
||||
@@ -237,9 +272,515 @@ describe('New Lockfile', () => {
|
||||
expect(lockfile.toString()).toBe(mockANewDedup);
|
||||
|
||||
await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(mockANew);
|
||||
await expect(lockfile.save()).resolves.toBeUndefined();
|
||||
await expect(lockfile.save('/yarn.lock')).resolves.toBeUndefined();
|
||||
await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(
|
||||
mockANewDedup,
|
||||
);
|
||||
});
|
||||
|
||||
it('should deduplicate and save mockANewLocal', async () => {
|
||||
mockFs({
|
||||
'/yarn.lock': mockANewLocal,
|
||||
});
|
||||
|
||||
const lockfile = await Lockfile.load('/yarn.lock');
|
||||
const result = lockfile.analyze({
|
||||
localPackages: new Map([
|
||||
[
|
||||
'b',
|
||||
{
|
||||
packageJson: { version: '2.0.1' },
|
||||
} as ExtendedPackage,
|
||||
],
|
||||
]),
|
||||
});
|
||||
expect(result).toEqual({
|
||||
invalidRanges: [],
|
||||
newRanges: [],
|
||||
newVersions: [
|
||||
{
|
||||
name: 'b',
|
||||
range: '^2',
|
||||
oldVersion: '2.0.0',
|
||||
newVersion: '0.0.0-use.local',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(lockfile.toString()).toBe(mockANewLocal);
|
||||
lockfile.replaceVersions(result.newVersions);
|
||||
expect(lockfile.toString()).toBe(mockANewLocalDedup);
|
||||
|
||||
await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(
|
||||
mockANewLocal,
|
||||
);
|
||||
await expect(lockfile.save('/yarn.lock')).resolves.toBeUndefined();
|
||||
await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(
|
||||
mockANewLocalDedup,
|
||||
);
|
||||
});
|
||||
|
||||
describe('diff', () => {
|
||||
const lockfileLegacyA = Lockfile.parse(`${LEGACY_HEADER}
|
||||
a@^1:
|
||||
version "1.0.1"
|
||||
resolved "https://my-registry/a-1.0.01.tgz#abc123"
|
||||
integrity sha512-xyz
|
||||
dependencies:
|
||||
b "^2"
|
||||
|
||||
b@3:
|
||||
version "3.0.1"
|
||||
integrity sha512-abc1
|
||||
|
||||
b@2.0.x:
|
||||
version "2.0.1"
|
||||
integrity sha512-abc2
|
||||
|
||||
b@^2:
|
||||
version "2.0.0"
|
||||
integrity sha512-abc3
|
||||
|
||||
c@^1:
|
||||
version "1.0.1"
|
||||
integrity x
|
||||
`);
|
||||
|
||||
const lockfileLegacyB = Lockfile.parse(`${LEGACY_HEADER}
|
||||
a@^1:
|
||||
version "1.0.1"
|
||||
resolved "https://my-registry/a-1.0.01.tgz#abc123"
|
||||
integrity sha512-xyz-other
|
||||
dependencies:
|
||||
b "^2"
|
||||
|
||||
b@2.0.x, b@^2:
|
||||
version "2.0.0"
|
||||
integrity sha512-abc3
|
||||
|
||||
b@4:
|
||||
version "4.0.0"
|
||||
integrity sha512-abc
|
||||
|
||||
d@^1:
|
||||
version "1.0.1"
|
||||
integrity x
|
||||
`);
|
||||
|
||||
const lockfileModernA = Lockfile.parse(`${MODERN_HEADER}
|
||||
"a@npm:^1":
|
||||
version: "1.0.1"
|
||||
resolved: "https://my-registry/a-1.0.01.tgz#abc123"
|
||||
checksum: sha512-xyz
|
||||
dependencies:
|
||||
b: "^2"
|
||||
|
||||
"b@npm:3":
|
||||
version: "3.0.1"
|
||||
checksum: sha512-abc1
|
||||
|
||||
"b@npm:2.0.x":
|
||||
version: "2.0.1"
|
||||
checksum: sha512-abc2
|
||||
|
||||
"b@npm:^2":
|
||||
version: "2.0.0"
|
||||
checksum: sha512-abc3
|
||||
|
||||
"c@npm:^1":
|
||||
version: "1.0.1"
|
||||
checksum: x
|
||||
`);
|
||||
|
||||
const lockfileModernB = Lockfile.parse(`${MODERN_HEADER}
|
||||
"a@npm:^1":
|
||||
version: "1.0.1"
|
||||
resolution: "a@npm:1.0.1"
|
||||
checksum: sha512-xyz-other
|
||||
dependencies:
|
||||
b: "^2"
|
||||
|
||||
"b@npm:2.0.x, b@npm:^2":
|
||||
version: "2.0.0"
|
||||
checksum: sha512-abc3
|
||||
|
||||
"b@npm:4":
|
||||
version: "4.0.0"
|
||||
checksum: sha512-abc
|
||||
|
||||
"d@npm:^1":
|
||||
version: "1.0.1"
|
||||
checksum: x
|
||||
`);
|
||||
|
||||
it('should diff two legacy lockfiles', async () => {
|
||||
expect(lockfileLegacyA.diff(lockfileLegacyB)).toEqual({
|
||||
added: [
|
||||
{ name: 'b', range: '3' },
|
||||
{ name: 'c', range: '^1' },
|
||||
],
|
||||
changed: [
|
||||
{ name: 'a', range: '^1' },
|
||||
{ name: 'b', range: '2.0.x' },
|
||||
],
|
||||
removed: [
|
||||
{ name: 'b', range: '4' },
|
||||
{ name: 'd', range: '^1' },
|
||||
],
|
||||
});
|
||||
expect(lockfileLegacyB.diff(lockfileLegacyA)).toEqual({
|
||||
added: [
|
||||
{ name: 'b', range: '4' },
|
||||
{ name: 'd', range: '^1' },
|
||||
],
|
||||
changed: [
|
||||
{ name: 'a', range: '^1' },
|
||||
{ name: 'b', range: '2.0.x' },
|
||||
],
|
||||
removed: [
|
||||
{ name: 'b', range: '3' },
|
||||
{ name: 'c', range: '^1' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should diff two modern lockfiles', async () => {
|
||||
expect(lockfileModernA.diff(lockfileModernB)).toEqual({
|
||||
added: [
|
||||
{ name: 'b', range: '3' },
|
||||
{ name: 'c', range: '^1' },
|
||||
],
|
||||
changed: [
|
||||
{ name: 'a', range: '^1' },
|
||||
{ name: 'b', range: '2.0.x' },
|
||||
],
|
||||
removed: [
|
||||
{ name: 'b', range: '4' },
|
||||
{ name: 'd', range: '^1' },
|
||||
],
|
||||
});
|
||||
expect(lockfileModernB.diff(lockfileModernA)).toEqual({
|
||||
added: [
|
||||
{ name: 'b', range: '4' },
|
||||
{ name: 'd', range: '^1' },
|
||||
],
|
||||
changed: [
|
||||
{ name: 'a', range: '^1' },
|
||||
{ name: 'b', range: '2.0.x' },
|
||||
],
|
||||
removed: [
|
||||
{ name: 'b', range: '3' },
|
||||
{ name: 'c', range: '^1' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should diff legacy and modern lockfiles', async () => {
|
||||
expect(lockfileLegacyA.diff(lockfileModernB)).toEqual({
|
||||
added: [
|
||||
{ name: 'b', range: '3' },
|
||||
{ name: 'c', range: '^1' },
|
||||
],
|
||||
changed: [
|
||||
{ name: 'a', range: '^1' },
|
||||
{ name: 'b', range: '2.0.x' },
|
||||
],
|
||||
removed: [
|
||||
{ name: 'b', range: '4' },
|
||||
{ name: 'd', range: '^1' },
|
||||
],
|
||||
});
|
||||
expect(lockfileLegacyB.diff(lockfileModernA)).toEqual({
|
||||
added: [
|
||||
{ name: 'b', range: '4' },
|
||||
{ name: 'd', range: '^1' },
|
||||
],
|
||||
changed: [
|
||||
{ name: 'a', range: '^1' },
|
||||
{ name: 'b', range: '2.0.x' },
|
||||
],
|
||||
removed: [
|
||||
{ name: 'b', range: '3' },
|
||||
{ name: 'c', range: '^1' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should diff modern and legacy lockfiles', async () => {
|
||||
expect(lockfileModernA.diff(lockfileLegacyB)).toEqual({
|
||||
added: [
|
||||
{ name: 'b', range: '3' },
|
||||
{ name: 'c', range: '^1' },
|
||||
],
|
||||
changed: [
|
||||
{ name: 'a', range: '^1' },
|
||||
{ name: 'b', range: '2.0.x' },
|
||||
],
|
||||
removed: [
|
||||
{ name: 'b', range: '4' },
|
||||
{ name: 'd', range: '^1' },
|
||||
],
|
||||
});
|
||||
expect(lockfileModernB.diff(lockfileLegacyA)).toEqual({
|
||||
added: [
|
||||
{ name: 'b', range: '4' },
|
||||
{ name: 'd', range: '^1' },
|
||||
],
|
||||
changed: [
|
||||
{ name: 'a', range: '^1' },
|
||||
{ name: 'b', range: '2.0.x' },
|
||||
],
|
||||
removed: [
|
||||
{ name: 'b', range: '3' },
|
||||
{ name: 'c', range: '^1' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle workspace ranges', async () => {
|
||||
const lockfile = `${MODERN_HEADER}
|
||||
"@backstage/app-defaults@workspace:^, @backstage/app-defaults@workspace:packages/app-defaults":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/app-defaults@workspace:packages/app-defaults"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/core-app-api": "workspace:^"
|
||||
"@backstage/core-components": "workspace:^"
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/plugin-permission-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@material-ui/core": ^4.12.2
|
||||
"@material-ui/icons": ^4.9.1
|
||||
"@testing-library/jest-dom": ^5.10.1
|
||||
"@testing-library/react": ^12.1.3
|
||||
"@types/node": ^16.11.26
|
||||
"@types/react": ^16.13.1 || ^17.0.0
|
||||
peerDependencies:
|
||||
react: ^16.13.1 || ^17.0.0
|
||||
react-dom: ^16.13.1 || ^17.0.0
|
||||
react-router-dom: 6.0.0-beta.0 || ^6.3.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/backend-app-api@workspace:^, @backstage/backend-app-api@workspace:packages/backend-app-api":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/backend-app-api@workspace:packages/backend-app-api"
|
||||
dependencies:
|
||||
"@backstage/backend-common": "workspace:^"
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-tasks": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/plugin-permission-node": "workspace:^"
|
||||
express: ^4.17.1
|
||||
express-promise-router: ^4.1.0
|
||||
winston: ^3.2.1
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
`;
|
||||
expect(Lockfile.parse(lockfile).diff(Lockfile.parse(lockfile))).toEqual({
|
||||
added: [],
|
||||
changed: [],
|
||||
removed: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSimplifiedDependencyGraph', () => {
|
||||
it('for modern lockfile', () => {
|
||||
expect(
|
||||
Lockfile.parse(
|
||||
`${MODERN_HEADER}
|
||||
"@backstage/app-defaults@workspace:^, @backstage/app-defaults@workspace:packages/app-defaults":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/app-defaults@workspace:packages/app-defaults"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/core-app-api": "workspace:^"
|
||||
"@backstage/core-components": "workspace:^"
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/plugin-permission-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@material-ui/core": ^4.12.2
|
||||
"@material-ui/icons": ^4.9.1
|
||||
"@testing-library/jest-dom": ^5.10.1
|
||||
"@testing-library/react": ^12.1.3
|
||||
"@types/node": ^16.11.26
|
||||
"@types/react": ^16.13.1 || ^17.0.0
|
||||
peerDependencies:
|
||||
react: ^16.13.1 || ^17.0.0
|
||||
react-dom: ^16.13.1 || ^17.0.0
|
||||
react-router-dom: 6.0.0-beta.0 || ^6.3.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/backend-app-api@workspace:^, @backstage/backend-app-api@workspace:packages/backend-app-api":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/backend-app-api@workspace:packages/backend-app-api"
|
||||
dependencies:
|
||||
"@backstage/backend-common": "workspace:^"
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-tasks": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/plugin-permission-node": "workspace:^"
|
||||
express: ^4.17.1
|
||||
express-promise-router: ^4.1.0
|
||||
winston: ^3.2.1
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
`,
|
||||
).createSimplifiedDependencyGraph(),
|
||||
).toEqual(
|
||||
new Map([
|
||||
[
|
||||
'@backstage/app-defaults',
|
||||
new Set([
|
||||
'@backstage/cli',
|
||||
'@backstage/core-app-api',
|
||||
'@backstage/core-components',
|
||||
'@backstage/core-plugin-api',
|
||||
'@backstage/plugin-permission-react',
|
||||
'@backstage/test-utils',
|
||||
'@backstage/theme',
|
||||
'@material-ui/core',
|
||||
'@material-ui/icons',
|
||||
'@testing-library/jest-dom',
|
||||
'@testing-library/react',
|
||||
'@types/node',
|
||||
'@types/react',
|
||||
'react',
|
||||
'react-dom',
|
||||
'react-router-dom',
|
||||
]),
|
||||
],
|
||||
[
|
||||
'@backstage/backend-app-api',
|
||||
new Set([
|
||||
'@backstage/backend-common',
|
||||
'@backstage/backend-plugin-api',
|
||||
'@backstage/backend-tasks',
|
||||
'@backstage/cli',
|
||||
'@backstage/errors',
|
||||
'@backstage/plugin-permission-node',
|
||||
'express',
|
||||
'express-promise-router',
|
||||
'winston',
|
||||
]),
|
||||
],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('for simple lockfile without dependencies', () => {
|
||||
expect(
|
||||
Lockfile.parse(
|
||||
`${MODERN_HEADER}
|
||||
"a@npm:^1":
|
||||
version: "1.0.1"
|
||||
|
||||
"b@npm:3":
|
||||
version: "3.0.1"
|
||||
|
||||
"b@npm:2.0.x":
|
||||
version: "2.0.1"
|
||||
checksum: sha512-abc2
|
||||
`,
|
||||
).createSimplifiedDependencyGraph(),
|
||||
).toEqual(
|
||||
new Map([
|
||||
['a', new Set()],
|
||||
['b', new Set()],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('for lockfile with dependencies', () => {
|
||||
expect(
|
||||
Lockfile.parse(
|
||||
`${MODERN_HEADER}
|
||||
"a@npm:^1":
|
||||
version: "1.0.1"
|
||||
dependencies:
|
||||
b: "^2"
|
||||
|
||||
"b@npm:3":
|
||||
version: "3.0.1"
|
||||
checksum: sha512-abc1
|
||||
|
||||
"b@npm:2.0.x":
|
||||
version: "2.0.1"
|
||||
checksum: sha512-abc2
|
||||
dependencies:
|
||||
c: "^1"
|
||||
|
||||
"b@npm:^2":
|
||||
version: "2.0.0"
|
||||
checksum: sha512-abc3
|
||||
peerDependencies:
|
||||
d: "^1"
|
||||
|
||||
"c@npm:^1":
|
||||
version: "1.0.1"
|
||||
|
||||
"d@npm:^1":
|
||||
version: "1.0.2"
|
||||
`,
|
||||
).createSimplifiedDependencyGraph(),
|
||||
).toEqual(
|
||||
new Map([
|
||||
['a', new Set(['b'])],
|
||||
['b', new Set(['c', 'd'])],
|
||||
['c', new Set()],
|
||||
['d', new Set()],
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('for legacy lockfile', () => {
|
||||
expect(
|
||||
Lockfile.parse(
|
||||
`${LEGACY_HEADER}
|
||||
a@^1:
|
||||
version "1.0.1"
|
||||
dependencies:
|
||||
b "^2"
|
||||
|
||||
b@3:
|
||||
version "3.0.1"
|
||||
integrity sha512-abc1
|
||||
|
||||
b@2.0.x:
|
||||
version "2.0.1"
|
||||
integrity sha512-abc2
|
||||
dependencies:
|
||||
c "^1"
|
||||
|
||||
b@^2:
|
||||
version "2.0.0"
|
||||
integrity sha512-abc3
|
||||
dependencies:
|
||||
d "^1"
|
||||
|
||||
c@^1:
|
||||
version "1.0.1"
|
||||
integrity x
|
||||
|
||||
d@^1:
|
||||
version "1.0.1"
|
||||
integrity x
|
||||
`,
|
||||
).createSimplifiedDependencyGraph(),
|
||||
).toEqual(
|
||||
new Map([
|
||||
['a', new Set(['b'])],
|
||||
['b', new Set(['c', 'd'])],
|
||||
['c', new Set()],
|
||||
['d', new Set()],
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import fs from 'fs-extra';
|
||||
import semver from 'semver';
|
||||
import { parseSyml, stringifySyml } from '@yarnpkg/parsers';
|
||||
import { stringify as legacyStringifyLockfile } from '@yarnpkg/lockfile';
|
||||
import { ExtendedPackage } from '../monorepo';
|
||||
|
||||
const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/;
|
||||
|
||||
@@ -25,14 +26,28 @@ type LockfileData = {
|
||||
[entry: string]: {
|
||||
version: string;
|
||||
resolved?: string;
|
||||
integrity?: string;
|
||||
integrity?: string /* old */;
|
||||
checksum?: string /* new */;
|
||||
dependencies?: { [name: string]: string };
|
||||
peerDependencies?: { [name: string]: string };
|
||||
};
|
||||
};
|
||||
|
||||
type LockfileQueryEntry = {
|
||||
range: string;
|
||||
version: string;
|
||||
dataKey: string;
|
||||
};
|
||||
|
||||
type LockfileDiffEntry = {
|
||||
name: string;
|
||||
range: string;
|
||||
};
|
||||
|
||||
type LockfileDiff = {
|
||||
added: LockfileDiffEntry[];
|
||||
changed: LockfileDiffEntry[];
|
||||
removed: LockfileDiffEntry[];
|
||||
};
|
||||
|
||||
/** Entries that have an invalid version range, for example an npm tag */
|
||||
@@ -64,20 +79,6 @@ type AnalyzeResult = {
|
||||
newRanges: AnalyzeResultNewRange[];
|
||||
};
|
||||
|
||||
function parseLockfile(lockfileContents: string) {
|
||||
try {
|
||||
return {
|
||||
object: parseSyml(lockfileContents),
|
||||
type: 'success',
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
object: null,
|
||||
type: err,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// the new yarn header is handled out of band of the parsing
|
||||
// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-core/sources/Project.ts#L1741-L1746
|
||||
const NEW_HEADER = `${[
|
||||
@@ -85,11 +86,6 @@ const NEW_HEADER = `${[
|
||||
`# Manual changes might be lost - proceed with caution!\n`,
|
||||
].join(``)}\n`;
|
||||
|
||||
function stringifyLockfile(data: LockfileData, legacy: boolean) {
|
||||
return legacy
|
||||
? legacyStringifyLockfile(data)
|
||||
: NEW_HEADER + stringifySyml(data);
|
||||
}
|
||||
// taken from yarn parser package
|
||||
// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-parsers/sources/syml.ts#L136
|
||||
const LEGACY_REGEX = /^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i;
|
||||
@@ -110,13 +106,19 @@ const SPECIAL_OBJECT_KEYS = [
|
||||
export class Lockfile {
|
||||
static async load(path: string) {
|
||||
const lockfileContents = await fs.readFile(path, 'utf8');
|
||||
const legacy = LEGACY_REGEX.test(lockfileContents);
|
||||
const lockfile = parseLockfile(lockfileContents);
|
||||
if (lockfile.type !== 'success') {
|
||||
throw new Error(`Failed yarn.lock parse with ${lockfile.type}`);
|
||||
return Lockfile.parse(lockfileContents);
|
||||
}
|
||||
|
||||
static parse(content: string) {
|
||||
const legacy = LEGACY_REGEX.test(content);
|
||||
|
||||
let data: LockfileData;
|
||||
try {
|
||||
data = parseSyml(content);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed yarn.lock parse, ${err}`);
|
||||
}
|
||||
|
||||
const data = lockfile.object as LockfileData;
|
||||
const packages = new Map<string, LockfileQueryEntry[]>();
|
||||
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
@@ -139,15 +141,14 @@ export class Lockfile {
|
||||
if (range.startsWith('npm:')) {
|
||||
range = range.slice('npm:'.length);
|
||||
}
|
||||
queries.push({ range, version: value.version });
|
||||
queries.push({ range, version: value.version, dataKey: key });
|
||||
}
|
||||
}
|
||||
|
||||
return new Lockfile(path, packages, data, legacy);
|
||||
return new Lockfile(packages, data, legacy);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
private readonly path: string,
|
||||
private readonly packages: Map<string, LockfileQueryEntry[]>,
|
||||
private readonly data: LockfileData,
|
||||
private readonly legacy: boolean = false,
|
||||
@@ -164,8 +165,11 @@ export class Lockfile {
|
||||
}
|
||||
|
||||
/** Analyzes the lockfile to identify possible actions and warnings for the entries */
|
||||
analyze(options?: { filter?: (name: string) => boolean }): AnalyzeResult {
|
||||
const { filter } = options ?? {};
|
||||
analyze(options: {
|
||||
filter?: (name: string) => boolean;
|
||||
localPackages: Map<string, ExtendedPackage>;
|
||||
}): AnalyzeResult {
|
||||
const { filter, localPackages } = options;
|
||||
const result: AnalyzeResult = {
|
||||
invalidRanges: [],
|
||||
newVersions: [],
|
||||
@@ -178,7 +182,9 @@ export class Lockfile {
|
||||
}
|
||||
|
||||
// Get rid of and signal any invalid ranges upfront
|
||||
const invalid = allEntries.filter(e => !semver.validRange(e.range));
|
||||
const invalid = allEntries.filter(
|
||||
e => !semver.validRange(e.range) && !e.range.startsWith('workspace:'),
|
||||
);
|
||||
result.invalidRanges.push(
|
||||
...invalid.map(({ range }) => ({ name, range })),
|
||||
);
|
||||
@@ -190,36 +196,55 @@ export class Lockfile {
|
||||
}
|
||||
|
||||
// Find all versions currently in use
|
||||
const versions = Array.from(new Set(entries.map(e => e.version))).sort(
|
||||
(v1, v2) => semver.rcompare(v1, v2),
|
||||
);
|
||||
const versions = Array.from(new Set(entries.map(e => e.version)))
|
||||
.map(v => {
|
||||
// Translate workspace:^ references to the actual version
|
||||
if (v === '0.0.0-use.local') {
|
||||
const local = localPackages.get(name);
|
||||
if (!local) {
|
||||
throw new Error(`No local package found for ${name}`);
|
||||
}
|
||||
if (!local.packageJson.version) {
|
||||
throw new Error(`No version found for local package ${name}`);
|
||||
}
|
||||
return {
|
||||
entryVersion: v,
|
||||
actualVersion: local.packageJson.version,
|
||||
};
|
||||
}
|
||||
return { entryVersion: v, actualVersion: v };
|
||||
})
|
||||
.sort((v1, v2) => semver.rcompare(v1.actualVersion, v2.actualVersion));
|
||||
|
||||
// If we're not using at least 2 different versions we're done
|
||||
if (versions.length < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(Rugvip): Support bumping into workspace ranges too
|
||||
const acceptedVersions = new Set<string>();
|
||||
for (const { version, range } of entries) {
|
||||
// Finds the highest matching version from the the known versions
|
||||
// TODO(Rugvip): We may want to select the version that satisfies the most ranges rather than the highest one
|
||||
const acceptedVersion = versions.find(v => semver.satisfies(v, range));
|
||||
const acceptedVersion = versions.find(v =>
|
||||
semver.satisfies(v.actualVersion, range),
|
||||
);
|
||||
if (!acceptedVersion) {
|
||||
throw new Error(
|
||||
`No existing version was accepted for range ${range}, searching through ${versions}, for package ${name}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (acceptedVersion !== version) {
|
||||
if (acceptedVersion.entryVersion !== version) {
|
||||
result.newVersions.push({
|
||||
name,
|
||||
range,
|
||||
newVersion: acceptedVersion,
|
||||
newVersion: acceptedVersion.entryVersion,
|
||||
oldVersion: version,
|
||||
});
|
||||
}
|
||||
|
||||
acceptedVersions.add(acceptedVersion);
|
||||
acceptedVersions.add(acceptedVersion.actualVersion);
|
||||
}
|
||||
|
||||
// If all ranges were able to accept the same version, we're done
|
||||
@@ -315,11 +340,91 @@ export class Lockfile {
|
||||
}
|
||||
}
|
||||
|
||||
async save() {
|
||||
await fs.writeFile(this.path, this.toString(), 'utf8');
|
||||
createSimplifiedDependencyGraph(): Map<string, Set<string>> {
|
||||
const graph = new Map<string, Set<string>>();
|
||||
|
||||
for (const [name, entries] of this.packages) {
|
||||
const dependencies = new Set(
|
||||
entries.flatMap(e => {
|
||||
const data = this.data[e.dataKey];
|
||||
return [
|
||||
...Object.keys(data?.dependencies ?? {}),
|
||||
...Object.keys(data?.peerDependencies ?? {}),
|
||||
];
|
||||
}),
|
||||
);
|
||||
graph.set(name, dependencies);
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff with another lockfile, returning entries that have been
|
||||
* added, changed, and removed compared to the other lockfile.
|
||||
*/
|
||||
diff(otherLockfile: Lockfile): LockfileDiff {
|
||||
const diff = {
|
||||
added: new Array<{ name: string; range: string }>(),
|
||||
changed: new Array<{ name: string; range: string }>(),
|
||||
removed: new Array<{ name: string; range: string }>(),
|
||||
};
|
||||
|
||||
// Keeps track of packages that only exist in this lockfile
|
||||
const remainingOldNames = new Set(this.packages.keys());
|
||||
|
||||
for (const [name, otherQueries] of otherLockfile.packages) {
|
||||
remainingOldNames.delete(name);
|
||||
|
||||
const thisQueries = this.packages.get(name);
|
||||
// If the packages doesn't exist in this lockfile, add all entries
|
||||
if (!thisQueries) {
|
||||
diff.removed.push(...otherQueries.map(q => ({ name, range: q.range })));
|
||||
continue;
|
||||
}
|
||||
|
||||
const remainingOldRanges = new Set(thisQueries.map(q => q.range));
|
||||
|
||||
for (const otherQuery of otherQueries) {
|
||||
remainingOldRanges.delete(otherQuery.range);
|
||||
|
||||
const thisQuery = thisQueries.find(q => q.range === otherQuery.range);
|
||||
if (!thisQuery) {
|
||||
diff.removed.push({ name, range: otherQuery.range });
|
||||
continue;
|
||||
}
|
||||
|
||||
const otherPkg = otherLockfile.data[otherQuery.dataKey];
|
||||
const thisPkg = this.data[thisQuery.dataKey];
|
||||
if (otherPkg && thisPkg) {
|
||||
const thisCheck = thisPkg.integrity || thisPkg.checksum;
|
||||
const otherCheck = otherPkg.integrity || otherPkg.checksum;
|
||||
if (thisCheck !== otherCheck) {
|
||||
diff.changed.push({ name, range: otherQuery.range });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const thisRange of remainingOldRanges) {
|
||||
diff.added.push({ name, range: thisRange });
|
||||
}
|
||||
}
|
||||
|
||||
for (const name of remainingOldNames) {
|
||||
const queries = this.packages.get(name) ?? [];
|
||||
diff.added.push(...queries.map(q => ({ name, range: q.range })));
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
async save(path: string) {
|
||||
await fs.writeFile(path, this.toString(), 'utf8');
|
||||
}
|
||||
|
||||
toString() {
|
||||
return stringifyLockfile(this.data, this.legacy);
|
||||
return this.legacy
|
||||
? legacyStringifyLockfile(this.data)
|
||||
: NEW_HEADER + stringifySyml(this.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,6 @@
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@microsoft/api-documenter": "^7.17.11",
|
||||
"@microsoft/api-extractor": "^7.23.0",
|
||||
"@microsoft/api-extractor-model": "^7.17.2",
|
||||
"@microsoft/tsdoc": "0.14.1",
|
||||
"@microsoft/tsdoc-config": "0.16.2",
|
||||
"chalk": "^4.0.0",
|
||||
"commander": "^9.1.0",
|
||||
"fs-extra": "10.1.0",
|
||||
@@ -53,6 +50,9 @@
|
||||
"mock-fs": "^5.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@microsoft/api-extractor-model": "*",
|
||||
"@microsoft/tsdoc": "*",
|
||||
"@microsoft/tsdoc-config": "*",
|
||||
"@rushstack/node-core-library": "*",
|
||||
"prettier": "^2.8.1",
|
||||
"typescript": "> 3.0.0"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import mockFs from 'mock-fs';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { resolve as resolvePath, join as joinPath } from 'path';
|
||||
import { resolvePackagePath, paths, findPackageDirs } from './paths';
|
||||
|
||||
describe('paths', () => {
|
||||
@@ -61,15 +61,15 @@ describe('paths', () => {
|
||||
});
|
||||
it('should return the path to the package if it exists and has a package.json', async () => {
|
||||
expect(await resolvePackagePath('packages/package-a')).toBe(
|
||||
'packages/package-a',
|
||||
joinPath('packages', 'package-a'),
|
||||
);
|
||||
expect(await resolvePackagePath('packages/package-b')).toBe(
|
||||
'packages/package-b',
|
||||
joinPath('packages', 'package-b'),
|
||||
);
|
||||
});
|
||||
it('should work with absolute paths', async () => {
|
||||
expect(await resolvePackagePath('/root/packages/package-a')).toBe(
|
||||
'packages/package-a',
|
||||
joinPath('packages', 'package-a'),
|
||||
);
|
||||
});
|
||||
it('should return undefined if the pat is not a directory', async () => {
|
||||
@@ -79,19 +79,19 @@ describe('paths', () => {
|
||||
describe('findPackageDirs', () => {
|
||||
it('should return only the given packages', async () => {
|
||||
expect(await findPackageDirs(['packages/package-a'])).toEqual([
|
||||
'packages/package-a',
|
||||
joinPath('packages', 'package-a'),
|
||||
]);
|
||||
});
|
||||
it('should return only the given packages when using glob patterns', async () => {
|
||||
expect(await findPackageDirs(['packages/*'])).toEqual([
|
||||
'packages/package-a',
|
||||
'packages/package-b',
|
||||
joinPath('packages', 'package-a'),
|
||||
joinPath('packages', 'package-b'),
|
||||
]);
|
||||
expect(await findPackageDirs(['packages/*', 'plugins/*'])).toEqual([
|
||||
'packages/package-a',
|
||||
'packages/package-b',
|
||||
'plugins/plugin-a',
|
||||
'plugins/plugin-b',
|
||||
joinPath('packages', 'package-a'),
|
||||
joinPath('packages', 'package-b'),
|
||||
joinPath('plugins', 'plugin-a'),
|
||||
joinPath('plugins', 'plugin-b'),
|
||||
]);
|
||||
});
|
||||
it('should return only the given packages when using absolute paths', async () => {
|
||||
@@ -100,15 +100,18 @@ describe('paths', () => {
|
||||
'/root/packages/package-a',
|
||||
'/root/plugins/plugin-b',
|
||||
]),
|
||||
).toEqual(['packages/package-a', 'plugins/plugin-b']);
|
||||
).toEqual([
|
||||
joinPath('packages', 'package-a'),
|
||||
joinPath('plugins', 'plugin-b'),
|
||||
]);
|
||||
});
|
||||
it('should return only the given packages when using absolute paths with glob patterns', async () => {
|
||||
expect(
|
||||
await findPackageDirs(['/root/packages/*', '/root/plugins/*-a']),
|
||||
).toEqual([
|
||||
'packages/package-a',
|
||||
'packages/package-b',
|
||||
'plugins/plugin-a',
|
||||
joinPath('packages', 'package-a'),
|
||||
joinPath('packages', 'package-b'),
|
||||
joinPath('plugins', 'plugin-a'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2cb9998: Fixed bug in `EntityOwnerPicker` and `EntityLifecyclePicker` that filtered on unavailable tags for the selected kind.
|
||||
- 2e701b3796: Internal refactor to use `react-router-dom` rather than `react-router`.
|
||||
- 6ffa47bb0a: Cleanup and small fixes for the kind selector
|
||||
- 19356df560: Updated dependency `zen-observable` to `^0.9.0`.
|
||||
|
||||
@@ -202,4 +202,33 @@ describe('<EntityTagPicker/>', () => {
|
||||
tags: new EntityTagFilter(['tag2']),
|
||||
});
|
||||
});
|
||||
it('removes tags from filters if there are none available', async () => {
|
||||
const updateFilters = jest.fn();
|
||||
const mockCatalogApiRefNoTags = {
|
||||
getEntityFacets: async () => ({
|
||||
facets: {
|
||||
'metadata.tags': {},
|
||||
},
|
||||
}),
|
||||
} as unknown as CatalogApi;
|
||||
|
||||
render(
|
||||
<TestApiProvider apis={[[catalogApiRef, mockCatalogApiRefNoTags]]}>
|
||||
<MockEntityListContextProvider
|
||||
value={{
|
||||
updateFilters,
|
||||
queryParameters: { tags: ['tag1'] },
|
||||
}}
|
||||
>
|
||||
<EntityTagPicker />
|
||||
</MockEntityListContextProvider>
|
||||
,
|
||||
</TestApiProvider>,
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(updateFilters).toHaveBeenLastCalledWith({
|
||||
tags: undefined,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,10 +93,14 @@ export const EntityTagPicker = (props: EntityTagPickerProps) => {
|
||||
}, [queryParamTags]);
|
||||
|
||||
useEffect(() => {
|
||||
const tags = Object.keys(availableTags ?? {});
|
||||
updateFilters({
|
||||
tags: selectedTags.length ? new EntityTagFilter(selectedTags) : undefined,
|
||||
tags:
|
||||
selectedTags.length && tags.length
|
||||
? new EntityTagFilter(selectedTags)
|
||||
: undefined,
|
||||
});
|
||||
}, [selectedTags, updateFilters]);
|
||||
}, [selectedTags, updateFilters, availableTags]);
|
||||
|
||||
if (!Object.keys(availableTags ?? {}).length) return null;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { Logger } from 'winston';
|
||||
@@ -15,6 +16,8 @@ export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
catalogApi?: CatalogApi;
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
|
||||
@@ -209,7 +209,7 @@ describe('CodeCoverageUtils', () => {
|
||||
} catch (error: any) {
|
||||
err = error;
|
||||
}
|
||||
expect(err?.message).toEqual('Content-Type missing');
|
||||
expect(err?.message).toEqual('Content-Type header missing');
|
||||
});
|
||||
|
||||
it('rejects unsupported content type', () => {
|
||||
@@ -223,7 +223,9 @@ describe('CodeCoverageUtils', () => {
|
||||
} catch (error: any) {
|
||||
err = error;
|
||||
}
|
||||
expect(err?.message).toEqual('Illegal Content-Type');
|
||||
expect(err?.message).toEqual(
|
||||
'Content-Type header "application/json" not supported, expected "text/xml" possibly followed by a charset',
|
||||
);
|
||||
});
|
||||
|
||||
it('parses the body', () => {
|
||||
|
||||
@@ -162,9 +162,11 @@ export class CoverageUtils {
|
||||
validateRequestBody(req: Request) {
|
||||
const contentType = req.headers['content-type'];
|
||||
if (!contentType) {
|
||||
throw new InputError('Content-Type missing');
|
||||
throw new InputError('Content-Type header missing');
|
||||
} else if (!contentType.match(/^text\/xml($|;)/)) {
|
||||
throw new InputError('Illegal Content-Type');
|
||||
throw new InputError(
|
||||
`Content-Type header "${contentType}" not supported, expected "text/xml" possibly followed by a charset`,
|
||||
);
|
||||
}
|
||||
const body = req.body;
|
||||
if (!body) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BranchHit, FileEntry } from '../types';
|
||||
import { JacocoSourceFile, JacocoXML } from './types';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface RouterOptions {
|
||||
database: PluginDatabaseManager;
|
||||
urlReader: UrlReader;
|
||||
logger: Logger;
|
||||
catalogApi?: CatalogApi;
|
||||
}
|
||||
|
||||
export interface CodeCoverageApi {
|
||||
@@ -59,7 +60,8 @@ export const makeRouter = async (
|
||||
|
||||
const codeCoverageDatabase = await CodeCoverageDatabase.create(database);
|
||||
const codecovUrl = await discovery.getExternalBaseUrl('code-coverage');
|
||||
const catalogApi: CatalogApi = new CatalogClient({ discoveryApi: discovery });
|
||||
const catalogApi =
|
||||
options.catalogApi ?? new CatalogClient({ discoveryApi: discovery });
|
||||
const scm = ScmIntegrations.fromConfig(config);
|
||||
|
||||
const router = Router();
|
||||
@@ -167,10 +169,10 @@ export const makeRouter = async (
|
||||
* /report?entity=component:default/mycomponent&coverageType=cobertura
|
||||
*/
|
||||
router.post('/report', async (req, res) => {
|
||||
const { entity, coverageType } = req.query;
|
||||
const entityLookup = await catalogApi.getEntityByRef(entity as string);
|
||||
if (!entityLookup) {
|
||||
throw new NotFoundError(`No entity found matching ${entity}`);
|
||||
const { entity: entityRef, coverageType } = req.query;
|
||||
const entity = await catalogApi.getEntityByRef(entityRef as string);
|
||||
if (!entity) {
|
||||
throw new NotFoundError(`No entity found matching ${entityRef}`);
|
||||
}
|
||||
|
||||
let converter: Converter;
|
||||
@@ -185,7 +187,7 @@ export const makeRouter = async (
|
||||
}
|
||||
|
||||
const { sourceLocation, vcs, scmFiles, body } =
|
||||
await utils.processCoveragePayload(entityLookup, req);
|
||||
await utils.processCoveragePayload(entity, req);
|
||||
|
||||
const files = converter.convert(body, scmFiles);
|
||||
if (!files || files.length === 0) {
|
||||
@@ -193,7 +195,7 @@ export const makeRouter = async (
|
||||
}
|
||||
|
||||
const coverage = await utils.buildCoverage(
|
||||
entityLookup,
|
||||
entity,
|
||||
sourceLocation,
|
||||
vcs,
|
||||
files,
|
||||
@@ -204,7 +206,7 @@ export const makeRouter = async (
|
||||
links: [
|
||||
{
|
||||
rel: 'coverage',
|
||||
href: `${codecovUrl}/report?entity=${entity}`,
|
||||
href: `${codecovUrl}/report?entity=${entityRef}`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
UrlReaders,
|
||||
useHotMemoize,
|
||||
} from '@backstage/backend-common';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { CompoundEntityRef, parseEntityRef } from '@backstage/catalog-model';
|
||||
import { Server } from 'http';
|
||||
import knexFactory from 'knex';
|
||||
import { Logger } from 'winston';
|
||||
@@ -52,13 +54,27 @@ export async function startStandaloneServer(
|
||||
return knex;
|
||||
});
|
||||
|
||||
const catalogApi = {
|
||||
async getEntityByRef(entityRef: string | CompoundEntityRef) {
|
||||
const { kind, namespace, name } = parseEntityRef(entityRef);
|
||||
return {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind,
|
||||
metadata: { name, namespace },
|
||||
spec: {},
|
||||
};
|
||||
},
|
||||
} as Partial<CatalogApi> as CatalogApi;
|
||||
|
||||
logger.debug('Starting application server...');
|
||||
|
||||
const router = await createRouter({
|
||||
database: { getClient: async () => db },
|
||||
config,
|
||||
discovery: SingleHostDiscovery.fromConfig(config),
|
||||
urlReader: UrlReaders.default({ logger, config }),
|
||||
logger,
|
||||
catalogApi,
|
||||
});
|
||||
|
||||
let service = createServiceBuilder(module)
|
||||
|
||||
@@ -44,8 +44,10 @@ import useAsync from 'react-use/lib/useAsync';
|
||||
const useStyles = makeStyles((theme: BackstageTheme) => ({
|
||||
graph: {
|
||||
minHeight: '100%',
|
||||
flex: 1,
|
||||
},
|
||||
graphWrapper: {
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
},
|
||||
organizationNode: {
|
||||
|
||||
@@ -86,7 +86,7 @@ export const ServicesTable = ({
|
||||
cellStyle: smColumnStyle,
|
||||
headerStyle: smColumnStyle,
|
||||
render: rowData => (
|
||||
<Typography>{rowData.uptime.uptimePercentage.p90}</Typography>
|
||||
<Typography>{rowData.uptime?.uptimePercentage?.p90 || ''}</Typography>
|
||||
),
|
||||
};
|
||||
const actionsColumn: TableColumn<Service> = {
|
||||
|
||||
@@ -73,16 +73,16 @@ spec:
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
catalogFilter:
|
||||
kind: Group
|
||||
system:
|
||||
title: System
|
||||
type: string
|
||||
description: System of the component
|
||||
ui:field: EntityPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- System
|
||||
catalogFilter:
|
||||
kind: System
|
||||
defaultKind: System
|
||||
|
||||
- title: Choose a location
|
||||
|
||||
@@ -74,16 +74,16 @@ spec:
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
catalogFilter:
|
||||
kind: Group
|
||||
system:
|
||||
title: System
|
||||
type: string
|
||||
description: System of the component
|
||||
ui:field: EntityPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- System
|
||||
catalogFilter:
|
||||
kind: System
|
||||
defaultKind: System
|
||||
|
||||
- title: Choose a location
|
||||
|
||||
@@ -73,16 +73,16 @@ spec:
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
catalogFilter:
|
||||
kind: Group
|
||||
system:
|
||||
title: System
|
||||
type: string
|
||||
description: System of the component
|
||||
ui:field: EntityPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- System
|
||||
catalogFilter:
|
||||
kind: System
|
||||
defaultKind: System
|
||||
|
||||
- title: Choose a location
|
||||
|
||||
@@ -49,8 +49,8 @@ spec:
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
catalogFilter:
|
||||
kind: Group
|
||||
|
||||
steps:
|
||||
- id: fetch-base
|
||||
|
||||
@@ -105,6 +105,10 @@ export const EntityPickerFieldExtension: FieldExtensionComponent<
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
catalogFilter?:
|
||||
| Record<string, string | string[]>
|
||||
| Record<string, string | string[]>[]
|
||||
| undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -116,6 +120,10 @@ export const EntityPickerFieldSchema: FieldSchema<
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
catalogFilter?:
|
||||
| Record<string, string | string[]>
|
||||
| Record<string, string | string[]>[]
|
||||
| undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -326,6 +334,10 @@ export const OwnerPickerFieldExtension: FieldExtensionComponent<
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
catalogFilter?:
|
||||
| Record<string, string | string[]>
|
||||
| Record<string, string | string[]>[]
|
||||
| undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -336,6 +348,10 @@ export const OwnerPickerFieldSchema: FieldSchema<
|
||||
defaultNamespace?: string | false | undefined;
|
||||
allowedKinds?: string[] | undefined;
|
||||
allowArbitraryValues?: boolean | undefined;
|
||||
catalogFilter?:
|
||||
| Record<string, string | string[]>
|
||||
| Record<string, string | string[]>[]
|
||||
| undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ parameters:
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
catalogFilter:
|
||||
kind: Group
|
||||
- title: Choose a location
|
||||
required:
|
||||
- repoUrl
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EntityFilterQuery } from '@backstage/catalog-client';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
@@ -34,7 +35,13 @@ describe('<EntityPicker />', () => {
|
||||
const schema = {};
|
||||
const required = false;
|
||||
let uiSchema: {
|
||||
'ui:options': { allowedKinds?: string[]; defaultKind?: string };
|
||||
'ui:options': {
|
||||
allowedKinds?: string[];
|
||||
defaultKind?: string;
|
||||
allowArbitraryValues?: boolean;
|
||||
defaultNamespace?: string | false;
|
||||
catalogFilter?: EntityFilterQuery;
|
||||
};
|
||||
};
|
||||
const rawErrors: string[] = [];
|
||||
const formData = undefined;
|
||||
@@ -66,7 +73,7 @@ describe('<EntityPicker />', () => {
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
describe('without allowedKinds', () => {
|
||||
describe('without allowedKinds and catalogFilter', () => {
|
||||
beforeEach(() => {
|
||||
uiSchema = { 'ui:options': {} };
|
||||
props = {
|
||||
@@ -136,4 +143,97 @@ describe('<EntityPicker />', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with catalogFilter', () => {
|
||||
beforeEach(() => {
|
||||
uiSchema = {
|
||||
'ui:options': {
|
||||
catalogFilter: [
|
||||
{
|
||||
kind: ['Group'],
|
||||
'metadata.name': 'test-entity',
|
||||
},
|
||||
{
|
||||
kind: ['User'],
|
||||
'metadata.name': 'test-entity',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
props = {
|
||||
onChange,
|
||||
schema,
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
formData,
|
||||
} as unknown as FieldProps<any>;
|
||||
|
||||
catalogApi.getEntities.mockResolvedValue({ items: entities });
|
||||
});
|
||||
|
||||
it('searches for a specific group entity', async () => {
|
||||
await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityPicker {...props} />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(catalogApi.getEntities).toHaveBeenCalledWith({
|
||||
filter: [
|
||||
{
|
||||
kind: ['Group'],
|
||||
'metadata.name': 'test-entity',
|
||||
},
|
||||
{
|
||||
kind: ['User'],
|
||||
'metadata.name': 'test-entity',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('catalogFilter should take precedence over allowedKinds', () => {
|
||||
beforeEach(() => {
|
||||
uiSchema = {
|
||||
'ui:options': {
|
||||
catalogFilter: [
|
||||
{
|
||||
kind: ['Group'],
|
||||
'metadata.name': 'test-group',
|
||||
},
|
||||
],
|
||||
allowedKinds: ['User'],
|
||||
},
|
||||
};
|
||||
props = {
|
||||
onChange,
|
||||
schema,
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
formData,
|
||||
} as unknown as FieldProps<any>;
|
||||
|
||||
catalogApi.getEntities.mockResolvedValue({ items: entities });
|
||||
});
|
||||
|
||||
it('searches for a Group entity', async () => {
|
||||
await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityPicker {...props} />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(catalogApi.getEntities).toHaveBeenCalledWith({
|
||||
filter: [
|
||||
{
|
||||
kind: ['Group'],
|
||||
'metadata.name': 'test-group',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { type EntityFilterQuery } from '@backstage/catalog-client';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
@@ -44,6 +45,11 @@ export const EntityPicker = (props: EntityPickerProps) => {
|
||||
idSchema,
|
||||
} = props;
|
||||
const allowedKinds = uiSchema['ui:options']?.allowedKinds;
|
||||
|
||||
const catalogFilter: EntityFilterQuery | undefined =
|
||||
uiSchema['ui:options']?.catalogFilter ||
|
||||
(allowedKinds && { kind: allowedKinds });
|
||||
|
||||
const defaultKind = uiSchema['ui:options']?.defaultKind;
|
||||
const defaultNamespace = uiSchema['ui:options']?.defaultNamespace;
|
||||
|
||||
@@ -51,7 +57,7 @@ export const EntityPicker = (props: EntityPickerProps) => {
|
||||
|
||||
const { value: entities, loading } = useAsync(() =>
|
||||
catalogApi.getEntities(
|
||||
allowedKinds ? { filter: { kind: allowedKinds } } : undefined,
|
||||
catalogFilter ? { filter: catalogFilter } : undefined,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -16,16 +16,28 @@
|
||||
import { z } from 'zod';
|
||||
import { makeFieldSchemaFromZod } from '../utils';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const entityQueryFilterExpressionSchema = z.record(
|
||||
z.string().or(z.array(z.string())),
|
||||
);
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const EntityPickerFieldSchema = makeFieldSchemaFromZod(
|
||||
z.string(),
|
||||
z.object({
|
||||
/**
|
||||
* @deprecated Use `catalogFilter` instead.
|
||||
*/
|
||||
allowedKinds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('List of kinds of entities to derive options from'),
|
||||
.describe(
|
||||
'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from',
|
||||
),
|
||||
defaultKind: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -42,6 +54,11 @@ export const EntityPickerFieldSchema = makeFieldSchemaFromZod(
|
||||
.describe(
|
||||
'The default namespace. Options with this namespace will not be prefixed.',
|
||||
),
|
||||
catalogFilter: z
|
||||
.array(entityQueryFilterExpressionSchema)
|
||||
.or(entityQueryFilterExpressionSchema)
|
||||
.optional()
|
||||
.describe('List of key-value filter expression for entities'),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type EntityFilterQuery } from '@backstage/catalog-client';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
@@ -32,7 +33,15 @@ describe('<OwnerPicker />', () => {
|
||||
const onChange = jest.fn();
|
||||
const schema = {};
|
||||
const required = false;
|
||||
let uiSchema: { 'ui:options': { allowedKinds?: string[] } };
|
||||
let uiSchema: {
|
||||
'ui:options': {
|
||||
allowedKinds?: string[];
|
||||
defaultKind?: string;
|
||||
allowArbitraryValues?: boolean;
|
||||
defaultNamespace?: string | false;
|
||||
catalogFilter?: EntityFilterQuery;
|
||||
};
|
||||
};
|
||||
const rawErrors: string[] = [];
|
||||
const formData = undefined;
|
||||
|
||||
@@ -63,7 +72,7 @@ describe('<OwnerPicker />', () => {
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
describe('without allowedKinds', () => {
|
||||
describe('without catalogFilter and allowedKinds', () => {
|
||||
beforeEach(() => {
|
||||
uiSchema = { 'ui:options': {} };
|
||||
props = {
|
||||
@@ -108,7 +117,7 @@ describe('<OwnerPicker />', () => {
|
||||
catalogApi.getEntities.mockResolvedValue({ items: entities });
|
||||
});
|
||||
|
||||
it('searches for users and groups', async () => {
|
||||
it('searches for users', async () => {
|
||||
await renderInTestApp(
|
||||
<Wrapper>
|
||||
<OwnerPicker {...props} />
|
||||
@@ -122,4 +131,93 @@ describe('<OwnerPicker />', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with catalogFilter', () => {
|
||||
beforeEach(() => {
|
||||
uiSchema = {
|
||||
'ui:options': {
|
||||
catalogFilter: [
|
||||
{
|
||||
kind: ['Group'],
|
||||
'spec.type': 'team',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
props = {
|
||||
onChange,
|
||||
schema,
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
formData,
|
||||
} as unknown as FieldProps<any>;
|
||||
|
||||
catalogApi.getEntities.mockResolvedValue({ items: entities });
|
||||
});
|
||||
|
||||
it('searches for group entities of type team', async () => {
|
||||
await renderInTestApp(
|
||||
<Wrapper>
|
||||
<OwnerPicker {...props} />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(catalogApi.getEntities).toHaveBeenCalledWith({
|
||||
filter: [
|
||||
{
|
||||
kind: ['Group'],
|
||||
'spec.type': 'team',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('catalogFilter should take precedence over allowedKinds', () => {
|
||||
beforeEach(() => {
|
||||
uiSchema = {
|
||||
'ui:options': {
|
||||
allowedKinds: ['User'],
|
||||
catalogFilter: [
|
||||
{
|
||||
kind: ['Group', 'User'],
|
||||
},
|
||||
{
|
||||
'spec.type': ['team', 'business-unit'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
props = {
|
||||
onChange,
|
||||
schema,
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
formData,
|
||||
} as unknown as FieldProps<any>;
|
||||
|
||||
catalogApi.getEntities.mockResolvedValue({ items: entities });
|
||||
});
|
||||
|
||||
it('searches for users and groups or teams and business units', async () => {
|
||||
await renderInTestApp(
|
||||
<Wrapper>
|
||||
<OwnerPicker {...props} />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(catalogApi.getEntities).toHaveBeenCalledWith({
|
||||
filter: [
|
||||
{
|
||||
kind: ['Group', 'User'],
|
||||
},
|
||||
{
|
||||
'spec.type': ['team', 'business-unit'],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,14 +33,16 @@ export const OwnerPicker = (props: OwnerPickerProps) => {
|
||||
} = props;
|
||||
|
||||
const defaultNamespace = uiSchema['ui:options']?.defaultNamespace;
|
||||
const allowedKinds = uiSchema['ui:options']?.allowedKinds;
|
||||
|
||||
const catalogFilter = uiSchema['ui:options']?.catalogFilter || {
|
||||
kind: allowedKinds || ['Group', 'User'],
|
||||
};
|
||||
|
||||
const ownerUiSchema = {
|
||||
...uiSchema,
|
||||
'ui:options': {
|
||||
allowedKinds: (uiSchema['ui:options']?.allowedKinds || [
|
||||
'Group',
|
||||
'User',
|
||||
]) as string[],
|
||||
catalogFilter,
|
||||
defaultKind: 'Group',
|
||||
allowArbitraryValues:
|
||||
uiSchema['ui:options']?.allowArbitraryValues ?? true,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import { z } from 'zod';
|
||||
import { makeFieldSchemaFromZod } from '../utils';
|
||||
import { entityQueryFilterExpressionSchema } from '../EntityPicker/schema';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -22,12 +23,15 @@ import { makeFieldSchemaFromZod } from '../utils';
|
||||
export const OwnerPickerFieldSchema = makeFieldSchemaFromZod(
|
||||
z.string(),
|
||||
z.object({
|
||||
/**
|
||||
* @deprecated Use `catalogFilter` instead.
|
||||
*/
|
||||
allowedKinds: z
|
||||
.array(z.string())
|
||||
.default(['Group', 'User'])
|
||||
.optional()
|
||||
.describe(
|
||||
'List of kinds of entities to derive options from. Defaults to Group and User',
|
||||
'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from. Defaults to Group and User',
|
||||
),
|
||||
allowArbitraryValues: z
|
||||
.boolean()
|
||||
@@ -39,6 +43,11 @@ export const OwnerPickerFieldSchema = makeFieldSchemaFromZod(
|
||||
.describe(
|
||||
'The default namespace. Options with this namespace will not be prefixed.',
|
||||
),
|
||||
catalogFilter: z
|
||||
.array(entityQueryFilterExpressionSchema)
|
||||
.or(entityQueryFilterExpressionSchema)
|
||||
.optional()
|
||||
.describe('List of key-value filter expression for entities'),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import type { RJSFValidationError } from '@rjsf/utils';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { NextFieldExtensionComponentProps } from '../../../extensions/types';
|
||||
|
||||
describe('Stepper', () => {
|
||||
it('should render the step titles for each step of the manifest', async () => {
|
||||
@@ -110,6 +111,98 @@ describe('Stepper', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should merge nested formData correctly in multiple steps', async () => {
|
||||
const Repo = ({
|
||||
onChange,
|
||||
}: NextFieldExtensionComponentProps<{ repository: string }, any>) => (
|
||||
<input
|
||||
aria-label="repo"
|
||||
type="text"
|
||||
onChange={e => onChange({ repository: e.target.value })}
|
||||
defaultValue=""
|
||||
/>
|
||||
);
|
||||
|
||||
const Owner = ({
|
||||
onChange,
|
||||
}: NextFieldExtensionComponentProps<{ owner: string }, any>) => (
|
||||
<input
|
||||
aria-label="owner"
|
||||
type="text"
|
||||
onChange={e => onChange({ owner: e.target.value })}
|
||||
defaultValue=""
|
||||
/>
|
||||
);
|
||||
|
||||
const manifest: TemplateParameterSchema = {
|
||||
steps: [
|
||||
{
|
||||
title: 'Step 1',
|
||||
schema: {
|
||||
properties: {
|
||||
first: {
|
||||
type: 'object',
|
||||
'ui:field': 'Repo',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Step 2',
|
||||
schema: {
|
||||
properties: {
|
||||
second: {
|
||||
type: 'object',
|
||||
'ui:field': 'Owner',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
title: 'React JSON Schema Form Test',
|
||||
};
|
||||
|
||||
const onComplete = jest.fn(async (values: Record<string, JsonValue>) => {
|
||||
expect(values).toEqual({
|
||||
first: { repository: 'Repo' },
|
||||
second: { owner: 'Owner' },
|
||||
});
|
||||
});
|
||||
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<Stepper
|
||||
manifest={manifest}
|
||||
onComplete={onComplete}
|
||||
extensions={[
|
||||
{ name: 'Repo', component: Repo },
|
||||
{ name: 'Owner', component: Owner },
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
await fireEvent.change(getByRole('textbox', { name: 'repo' }), {
|
||||
target: { value: 'Repo' },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(getByRole('button', { name: 'Next' }));
|
||||
});
|
||||
|
||||
await fireEvent.change(getByRole('textbox', { name: 'owner' }), {
|
||||
target: { value: 'Owner' },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(getByRole('button', { name: 'Review' }));
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await fireEvent.click(getByRole('button', { name: 'Create' }));
|
||||
});
|
||||
|
||||
expect(onComplete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render custom field extensions properly', async () => {
|
||||
const MockComponent = () => {
|
||||
return <h1>im a custom field extension</h1>;
|
||||
|
||||
@@ -26,9 +26,9 @@ import {
|
||||
Button,
|
||||
makeStyles,
|
||||
} from '@material-ui/core';
|
||||
import { withTheme } from '@rjsf/core-v5';
|
||||
import { type IChangeEvent, withTheme } from '@rjsf/core-v5';
|
||||
import { ErrorSchema, FieldValidation } from '@rjsf/utils';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { NextFieldExtensionOptions } from '../../../extensions';
|
||||
import { TemplateParameterSchema } from '../../../types';
|
||||
import { createAsyncValidators } from './createAsyncValidators';
|
||||
@@ -36,7 +36,6 @@ import { useTemplateSchema } from './useTemplateSchema';
|
||||
import { ReviewState } from './ReviewState';
|
||||
import validator from '@rjsf/validator-ajv6';
|
||||
import { selectedTemplateRouteRef } from '../../../routes';
|
||||
import { getDefaultFormState } from '@rjsf/utils';
|
||||
import { useFormData } from './useFormData';
|
||||
import { FormProps } from '../../types';
|
||||
|
||||
@@ -102,6 +101,12 @@ export const Stepper = (props: StepperProps) => {
|
||||
setActiveStep(prevActiveStep => prevActiveStep - 1);
|
||||
};
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: IChangeEvent) =>
|
||||
setFormState(current => ({ ...current, ...e.formData })),
|
||||
[setFormState],
|
||||
);
|
||||
|
||||
const handleNext = async ({
|
||||
formData,
|
||||
}: {
|
||||
@@ -111,18 +116,7 @@ export const Stepper = (props: StepperProps) => {
|
||||
// to display it's own loading? Or should we grey out the entire form.
|
||||
setErrors(undefined);
|
||||
|
||||
const schema = steps[activeStep]?.schema;
|
||||
const rootSchema = steps[activeStep]?.mergedSchema;
|
||||
|
||||
const newFormData = getDefaultFormState(
|
||||
validator,
|
||||
schema,
|
||||
formData,
|
||||
rootSchema,
|
||||
true,
|
||||
);
|
||||
|
||||
const returnedValidation = await validation(newFormData);
|
||||
const returnedValidation = await validation(formData);
|
||||
|
||||
const hasErrors = Object.values(returnedValidation).some(
|
||||
i => i.__errors?.length,
|
||||
@@ -138,7 +132,7 @@ export const Stepper = (props: StepperProps) => {
|
||||
return stepNum;
|
||||
});
|
||||
}
|
||||
setFormState(current => ({ ...current, ...newFormData }));
|
||||
setFormState(current => ({ ...current, ...formData }));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -165,6 +159,7 @@ export const Stepper = (props: StepperProps) => {
|
||||
onSubmit={handleNext}
|
||||
fields={extensions}
|
||||
showErrorList={false}
|
||||
onChange={handleChange}
|
||||
{...(props.FormProps ?? {})}
|
||||
>
|
||||
<div className={styles.footer}>
|
||||
|
||||
+29
@@ -250,4 +250,33 @@ describe('ElasticSearchSearchEngineIndexer', () => {
|
||||
// Final deletion shouldn't be called.
|
||||
expect(deleteSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('handles bulk client rejection', async () => {
|
||||
// Given an ES client wrapper that rejects an error
|
||||
const expectedError = new Error('HTTP Timeout');
|
||||
const mockClientWrapper = ElasticSearchClientWrapper.fromClientOptions({
|
||||
node: 'http://localhost:9200',
|
||||
Connection: mock.getConnection(),
|
||||
});
|
||||
mockClientWrapper.bulk = jest.fn().mockRejectedValue(expectedError);
|
||||
|
||||
// And a search engine indexer that uses that client wrapper
|
||||
indexer = new ElasticSearchSearchEngineIndexer({
|
||||
type: 'some-type',
|
||||
indexPrefix: '',
|
||||
indexSeparator: '-index__',
|
||||
alias: 'some-type-index__search',
|
||||
logger: getVoidLogger(),
|
||||
elasticSearchClientWrapper: mockClientWrapper,
|
||||
batchSize: 1000,
|
||||
});
|
||||
|
||||
// When the indexer is run in the test pipeline
|
||||
const { error } = await TestPipeline.fromIndexer(indexer)
|
||||
.withDocuments([{ title: 'a', location: 'a', text: '/a' }])
|
||||
.execute();
|
||||
|
||||
// Then the pipeline should have received the expected error
|
||||
expect(error).toBe(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
+41
-5
@@ -45,7 +45,6 @@ function duration(startTimestamp: [number, number]): string {
|
||||
* @public
|
||||
*/
|
||||
export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer {
|
||||
private received: number = 0;
|
||||
private processed: number = 0;
|
||||
private removableIndices: string[] = [];
|
||||
|
||||
@@ -59,10 +58,13 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer {
|
||||
private readonly logger: Logger;
|
||||
private readonly sourceStream: Readable;
|
||||
private readonly elasticSearchClientWrapper: ElasticSearchClientWrapper;
|
||||
private configuredBatchSize: number;
|
||||
private bulkResult: Promise<any>;
|
||||
private bulkClientError?: Error;
|
||||
|
||||
constructor(options: ElasticSearchSearchEngineIndexerOptions) {
|
||||
super({ batchSize: options.batchSize });
|
||||
this.configuredBatchSize = options.batchSize;
|
||||
this.logger = options.logger.child({ documentType: options.type });
|
||||
this.startTimestamp = process.hrtime();
|
||||
this.type = options.type;
|
||||
@@ -94,6 +96,11 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer {
|
||||
},
|
||||
refreshOnCompletion: that.indexName,
|
||||
});
|
||||
|
||||
// Safely catch errors thrown by the bulk helper client, e.g. HTTP timeouts
|
||||
this.bulkResult.catch(e => {
|
||||
this.bulkClientError = e;
|
||||
});
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
@@ -115,7 +122,6 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer {
|
||||
async index(documents: IndexableDocument[]): Promise<void> {
|
||||
await this.isReady();
|
||||
documents.forEach(document => {
|
||||
this.received++;
|
||||
this.sourceStream.push(document);
|
||||
});
|
||||
}
|
||||
@@ -197,11 +203,41 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer {
|
||||
* backpressure in other parts of the indexing pipeline.
|
||||
*/
|
||||
private isReady(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
// Early exit if the underlying ES client encountered an error.
|
||||
if (this.bulkClientError) {
|
||||
return Promise.reject(this.bulkClientError);
|
||||
}
|
||||
|
||||
// Optimization: if the stream that ES reads from has fewer docs queued
|
||||
// than the configured batch size, continue early to allow more docs to be
|
||||
// queued
|
||||
if (this.sourceStream.readableLength < this.configuredBatchSize) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Otherwise, continue periodically checking the stream queue to see if
|
||||
// ES has consumed the documents and continue when it's ready for more.
|
||||
return new Promise((isReady, abort) => {
|
||||
let streamLengthChecks = 0;
|
||||
const interval = setInterval(() => {
|
||||
if (this.received === this.processed) {
|
||||
streamLengthChecks++;
|
||||
|
||||
if (this.sourceStream.readableLength < this.configuredBatchSize) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
isReady();
|
||||
}
|
||||
|
||||
// Do not allow this interval to loop endlessly; anything longer than 5
|
||||
// minutes likely indicates an unrecoverable error in ES; direct the
|
||||
// user to inspect ES logs for more clues and abort in order to allow
|
||||
// the index to be cleaned up.
|
||||
if (streamLengthChecks >= 6000) {
|
||||
clearInterval(interval);
|
||||
abort(
|
||||
new Error(
|
||||
'Exceeded 5 minutes waiting for elastic to be ready to accept more documents. Check the elastic logs for possible problems.',
|
||||
),
|
||||
);
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
|
||||
@@ -408,15 +408,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/client-cognito-identity@npm:3.235.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/client-cognito-identity@npm:3.235.0"
|
||||
"@aws-sdk/client-cognito-identity@npm:3.236.0":
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/client-cognito-identity@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-crypto/sha256-browser": 2.0.0
|
||||
"@aws-crypto/sha256-js": 2.0.0
|
||||
"@aws-sdk/client-sts": 3.235.0
|
||||
"@aws-sdk/client-sts": 3.236.0
|
||||
"@aws-sdk/config-resolver": 3.234.0
|
||||
"@aws-sdk/credential-provider-node": 3.235.0
|
||||
"@aws-sdk/credential-provider-node": 3.236.0
|
||||
"@aws-sdk/fetch-http-handler": 3.226.0
|
||||
"@aws-sdk/hash-node": 3.226.0
|
||||
"@aws-sdk/invalid-dependency": 3.226.0
|
||||
@@ -448,20 +448,20 @@ __metadata:
|
||||
"@aws-sdk/util-utf8-browser": 3.188.0
|
||||
"@aws-sdk/util-utf8-node": 3.208.0
|
||||
tslib: ^2.3.1
|
||||
checksum: 4637132cc1a95823867c5ee80b35c7b84ddeed83c0778c2d7c197b26f24899641c825926c886c293f36d62e6b72be03ee091ac11cc9dbd875ad85936548ee178
|
||||
checksum: 808339bd0113f65674dea0d65af25c350ff5a8f2595f9dd11215b6e457df5d243db156042dc4bfbb39b302d6f871a71fbbed20f403207fb6e4c9e444eb802bf1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/client-s3@npm:^3.208.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/client-s3@npm:3.235.0"
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/client-s3@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-crypto/sha1-browser": 2.0.0
|
||||
"@aws-crypto/sha256-browser": 2.0.0
|
||||
"@aws-crypto/sha256-js": 2.0.0
|
||||
"@aws-sdk/client-sts": 3.235.0
|
||||
"@aws-sdk/client-sts": 3.236.0
|
||||
"@aws-sdk/config-resolver": 3.234.0
|
||||
"@aws-sdk/credential-provider-node": 3.235.0
|
||||
"@aws-sdk/credential-provider-node": 3.236.0
|
||||
"@aws-sdk/eventstream-serde-browser": 3.226.0
|
||||
"@aws-sdk/eventstream-serde-config-resolver": 3.226.0
|
||||
"@aws-sdk/eventstream-serde-node": 3.226.0
|
||||
@@ -511,19 +511,19 @@ __metadata:
|
||||
"@aws-sdk/xml-builder": 3.201.0
|
||||
fast-xml-parser: 4.0.11
|
||||
tslib: ^2.3.1
|
||||
checksum: b16ce0a34a4d302edbc9cae91296a90a907511d6f0985feccf2196d537e1f1bfff6bfc0dafcb7ea1d5d7d0afd79d843cc15f8a5f87debd08852de338c698169b
|
||||
checksum: 6a8953c229a1824bf1639dfa1a593bff3419c2c338da8df956d5cd3098dc3c1236ee63564347a22f40dc5947322b3f315ae7e39dd9c115ccf8a28199168c6d3b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/client-sqs@npm:^3.208.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/client-sqs@npm:3.235.0"
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/client-sqs@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-crypto/sha256-browser": 2.0.0
|
||||
"@aws-crypto/sha256-js": 2.0.0
|
||||
"@aws-sdk/client-sts": 3.235.0
|
||||
"@aws-sdk/client-sts": 3.236.0
|
||||
"@aws-sdk/config-resolver": 3.234.0
|
||||
"@aws-sdk/credential-provider-node": 3.235.0
|
||||
"@aws-sdk/credential-provider-node": 3.236.0
|
||||
"@aws-sdk/fetch-http-handler": 3.226.0
|
||||
"@aws-sdk/hash-node": 3.226.0
|
||||
"@aws-sdk/invalid-dependency": 3.226.0
|
||||
@@ -558,13 +558,13 @@ __metadata:
|
||||
"@aws-sdk/util-utf8-node": 3.208.0
|
||||
fast-xml-parser: 4.0.11
|
||||
tslib: ^2.3.1
|
||||
checksum: 04da2ab0a850fa74265b9ab91b14fdf470d7f2850e31866b3582b83a01991bd9555b370e34bceba2445910b2d60cf8e17d7ead1f5caa94987e4114db949548f8
|
||||
checksum: c1d3eb3886d8d799afd081cf76822627e359495daa94edb615a001704933b31d276249828c5e5823af49ba08826875a4c768d8e6b4493abe9b65583a585839b7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/client-sso-oidc@npm:3.235.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/client-sso-oidc@npm:3.235.0"
|
||||
"@aws-sdk/client-sso-oidc@npm:3.236.0":
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/client-sso-oidc@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-crypto/sha256-browser": 2.0.0
|
||||
"@aws-crypto/sha256-js": 2.0.0
|
||||
@@ -599,13 +599,13 @@ __metadata:
|
||||
"@aws-sdk/util-utf8-browser": 3.188.0
|
||||
"@aws-sdk/util-utf8-node": 3.208.0
|
||||
tslib: ^2.3.1
|
||||
checksum: c62558375fb6d3ce44136019de99e5134805894a80cec191e145003c0e065eb52ba0a288a62884371d6a69370f92df522b120e1c5428520c09ba0f4cdc5b91b7
|
||||
checksum: 6fbbebb37547bafc9345c1fd03692728d6b6015f7583f22b2542beaac7f395ab29af884f1a353e78dd147ebf8b109b1d6678f9d1ae18c2b70739e9ceee6781ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/client-sso@npm:3.235.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/client-sso@npm:3.235.0"
|
||||
"@aws-sdk/client-sso@npm:3.236.0":
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/client-sso@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-crypto/sha256-browser": 2.0.0
|
||||
"@aws-crypto/sha256-js": 2.0.0
|
||||
@@ -640,18 +640,18 @@ __metadata:
|
||||
"@aws-sdk/util-utf8-browser": 3.188.0
|
||||
"@aws-sdk/util-utf8-node": 3.208.0
|
||||
tslib: ^2.3.1
|
||||
checksum: 9852704cc383cbc8e44013eed6de514f60dc4f70ab89ceea79971b4b286db6b923648e8e4169d248f0888efed19c7c5fcc5748177577635d07080fd4980431d7
|
||||
checksum: 15cb25a3c12fe0f11ed320c8f02f72ee0d43291a14c06fce81f608fbc75a85785698fbffc3112e7e92ece25460d24670ba345535b43003b20798a0fcc8a7b31d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/client-sts@npm:3.235.0, @aws-sdk/client-sts@npm:^3.208.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/client-sts@npm:3.235.0"
|
||||
"@aws-sdk/client-sts@npm:3.236.0, @aws-sdk/client-sts@npm:^3.208.0":
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/client-sts@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-crypto/sha256-browser": 2.0.0
|
||||
"@aws-crypto/sha256-js": 2.0.0
|
||||
"@aws-sdk/config-resolver": 3.234.0
|
||||
"@aws-sdk/credential-provider-node": 3.235.0
|
||||
"@aws-sdk/credential-provider-node": 3.236.0
|
||||
"@aws-sdk/fetch-http-handler": 3.226.0
|
||||
"@aws-sdk/hash-node": 3.226.0
|
||||
"@aws-sdk/invalid-dependency": 3.226.0
|
||||
@@ -685,7 +685,7 @@ __metadata:
|
||||
"@aws-sdk/util-utf8-node": 3.208.0
|
||||
fast-xml-parser: 4.0.11
|
||||
tslib: ^2.3.1
|
||||
checksum: 9a2ec3240bd01fd1ed95b2c6766ea8d141e4b85a6535fcbb5c57ac725ef58c2d1c1403a61c336adbb1eceb5dd306ac1d35d7b8f25ed1ebc544a14ad14fb038a7
|
||||
checksum: 98463fe50a588032b82be5289445c6e7250bd947076e5981ba7c126386d567f3b1ea9a9652c032da919503cdba2c3b517061911ddffae0c91f880f7eabaeaa08
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -702,15 +702,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/credential-provider-cognito-identity@npm:3.235.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/credential-provider-cognito-identity@npm:3.235.0"
|
||||
"@aws-sdk/credential-provider-cognito-identity@npm:3.236.0":
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/credential-provider-cognito-identity@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-sdk/client-cognito-identity": 3.235.0
|
||||
"@aws-sdk/client-cognito-identity": 3.236.0
|
||||
"@aws-sdk/property-provider": 3.226.0
|
||||
"@aws-sdk/types": 3.226.0
|
||||
tslib: ^2.3.1
|
||||
checksum: 85d348faa5161282d226040315aaff1a754d3bbd23ccb9131f021a5f6a473be76b80e2b3f5132d3b0a119732822d6b76829626d64e6c71c4d61d1fcbeb6e0080
|
||||
checksum: 29d3178f490cbb711553103c0fb544604c5e70e36395f1edc6b9bd2ee0e928160b911b4893fe89418c182548d0dddfbd44e9ef629ad690ab1724a2a693368c16
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -738,38 +738,38 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/credential-provider-ini@npm:3.235.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/credential-provider-ini@npm:3.235.0"
|
||||
"@aws-sdk/credential-provider-ini@npm:3.236.0":
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/credential-provider-ini@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-sdk/credential-provider-env": 3.226.0
|
||||
"@aws-sdk/credential-provider-imds": 3.226.0
|
||||
"@aws-sdk/credential-provider-process": 3.226.0
|
||||
"@aws-sdk/credential-provider-sso": 3.235.0
|
||||
"@aws-sdk/credential-provider-sso": 3.236.0
|
||||
"@aws-sdk/credential-provider-web-identity": 3.226.0
|
||||
"@aws-sdk/property-provider": 3.226.0
|
||||
"@aws-sdk/shared-ini-file-loader": 3.226.0
|
||||
"@aws-sdk/types": 3.226.0
|
||||
tslib: ^2.3.1
|
||||
checksum: c910746d90ec363691dc99d53594beb49ea8562ef69c5a2e9a1702febc7d870cfe5b0f441afdae2bed643a4e8901b82f965660cdd70077bb006978fdafddeb05
|
||||
checksum: e748aba37dab11ed395fa60c7eee119b997e4c64b1a838c93258cfe6a6e289dfa0aa20d292b94d06da8148b7cb32604b24f84aadc2a389bf8248c64d80ef44d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/credential-provider-node@npm:3.235.0, @aws-sdk/credential-provider-node@npm:^3.208.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/credential-provider-node@npm:3.235.0"
|
||||
"@aws-sdk/credential-provider-node@npm:3.236.0, @aws-sdk/credential-provider-node@npm:^3.208.0":
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/credential-provider-node@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-sdk/credential-provider-env": 3.226.0
|
||||
"@aws-sdk/credential-provider-imds": 3.226.0
|
||||
"@aws-sdk/credential-provider-ini": 3.235.0
|
||||
"@aws-sdk/credential-provider-ini": 3.236.0
|
||||
"@aws-sdk/credential-provider-process": 3.226.0
|
||||
"@aws-sdk/credential-provider-sso": 3.235.0
|
||||
"@aws-sdk/credential-provider-sso": 3.236.0
|
||||
"@aws-sdk/credential-provider-web-identity": 3.226.0
|
||||
"@aws-sdk/property-provider": 3.226.0
|
||||
"@aws-sdk/shared-ini-file-loader": 3.226.0
|
||||
"@aws-sdk/types": 3.226.0
|
||||
tslib: ^2.3.1
|
||||
checksum: bfea379a5e66c311c57a1f7390b2e508d42f8cf3e1a48a5b9585b33c76339c7e8806dba413c7d5bf6d669ffd05a4c55ef705f92bf4687ba8f5e359c5d3c7495e
|
||||
checksum: d5a26b40a985edfef5006efde921d58b78829b67eed9fea56e108b99eec437a02721b93ef0e2dcd9a4739716c85d8db75935a71d2afa1190b0f1d0010511f649
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -785,17 +785,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/credential-provider-sso@npm:3.235.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/credential-provider-sso@npm:3.235.0"
|
||||
"@aws-sdk/credential-provider-sso@npm:3.236.0":
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/credential-provider-sso@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-sdk/client-sso": 3.235.0
|
||||
"@aws-sdk/client-sso": 3.236.0
|
||||
"@aws-sdk/property-provider": 3.226.0
|
||||
"@aws-sdk/shared-ini-file-loader": 3.226.0
|
||||
"@aws-sdk/token-providers": 3.235.0
|
||||
"@aws-sdk/token-providers": 3.236.0
|
||||
"@aws-sdk/types": 3.226.0
|
||||
tslib: ^2.3.1
|
||||
checksum: 358fdda689c1f9be768516ca39de17e7fa7832d3d89d8893dd58f5d3377908ee2580155c1ba984c5ed21a410cda681bd22c7c74876af06b3496641a98b62353f
|
||||
checksum: 215e0cd08c4dca453a80ce695bf4dddb94ce7562100bfa64e2ba5406ab3aeb6cf92882bd714b6ee95d3f13c45f3286eb616d32e1ffc58728f1e21e7eb171b301
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -811,25 +811,25 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/credential-providers@npm:^3.208.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/credential-providers@npm:3.235.0"
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/credential-providers@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-sdk/client-cognito-identity": 3.235.0
|
||||
"@aws-sdk/client-sso": 3.235.0
|
||||
"@aws-sdk/client-sts": 3.235.0
|
||||
"@aws-sdk/credential-provider-cognito-identity": 3.235.0
|
||||
"@aws-sdk/client-cognito-identity": 3.236.0
|
||||
"@aws-sdk/client-sso": 3.236.0
|
||||
"@aws-sdk/client-sts": 3.236.0
|
||||
"@aws-sdk/credential-provider-cognito-identity": 3.236.0
|
||||
"@aws-sdk/credential-provider-env": 3.226.0
|
||||
"@aws-sdk/credential-provider-imds": 3.226.0
|
||||
"@aws-sdk/credential-provider-ini": 3.235.0
|
||||
"@aws-sdk/credential-provider-node": 3.235.0
|
||||
"@aws-sdk/credential-provider-ini": 3.236.0
|
||||
"@aws-sdk/credential-provider-node": 3.236.0
|
||||
"@aws-sdk/credential-provider-process": 3.226.0
|
||||
"@aws-sdk/credential-provider-sso": 3.235.0
|
||||
"@aws-sdk/credential-provider-sso": 3.236.0
|
||||
"@aws-sdk/credential-provider-web-identity": 3.226.0
|
||||
"@aws-sdk/property-provider": 3.226.0
|
||||
"@aws-sdk/shared-ini-file-loader": 3.226.0
|
||||
"@aws-sdk/types": 3.226.0
|
||||
tslib: ^2.3.1
|
||||
checksum: 3c8436f78a4ddcf295b06d77ed3cb912a8ae56ee047526663f8316dde6b646095c8288480214c9e3f68cb98772511cb7fcec6f8c4100fba899c495a9429c3ba2
|
||||
checksum: 3105661946fa12322b18a2227f26a8b734072850882e771493b398e714b433c024873a4495a18d55a4213022b07c658cb7ed38859b5fa152fc2ce1e02b408ca1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -967,8 +967,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/lib-storage@npm:^3.208.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/lib-storage@npm:3.235.0"
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/lib-storage@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-sdk/middleware-endpoint": 3.226.0
|
||||
"@aws-sdk/smithy-client": 3.234.0
|
||||
@@ -979,7 +979,7 @@ __metadata:
|
||||
peerDependencies:
|
||||
"@aws-sdk/abort-controller": ^3.0.0
|
||||
"@aws-sdk/client-s3": ^3.0.0
|
||||
checksum: a9a8993fdc8c05769a2f0c5c838e9b2ba72216f234e67c1a8bcebf8f812ab397d48c8b118959f4d9918ad8811fdf93b3b47812075b0995af8c11cb26c0f3c305
|
||||
checksum: 783b67c4605a4f7d4072ce38b2b2416a0a6771784f9ba5b1608be3777e7d21a18c820fff94218856607f984c453376e25e80393ef233365476850e354f8e15f8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1355,16 +1355,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@aws-sdk/token-providers@npm:3.235.0":
|
||||
version: 3.235.0
|
||||
resolution: "@aws-sdk/token-providers@npm:3.235.0"
|
||||
"@aws-sdk/token-providers@npm:3.236.0":
|
||||
version: 3.236.0
|
||||
resolution: "@aws-sdk/token-providers@npm:3.236.0"
|
||||
dependencies:
|
||||
"@aws-sdk/client-sso-oidc": 3.235.0
|
||||
"@aws-sdk/client-sso-oidc": 3.236.0
|
||||
"@aws-sdk/property-provider": 3.226.0
|
||||
"@aws-sdk/shared-ini-file-loader": 3.226.0
|
||||
"@aws-sdk/types": 3.226.0
|
||||
tslib: ^2.3.1
|
||||
checksum: 5b9cd8cfd697f04a7c628329af1cc5003ec25fe08626148c55389311b53ecbaa47b83f2c6b832fa847f9db2ca5a3f63bcb8a1059b1b3b3590010acf3d4610896
|
||||
checksum: c5e6c88f22d35002f2d575d23ba5a530ee195b7220d7c0c58603bc2fafb6632a6755bc863cb7519eadda110d2f3193a144f34cc0236df285fcde40c980ba653a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8231,9 +8231,6 @@ __metadata:
|
||||
"@manypkg/get-packages": ^1.1.3
|
||||
"@microsoft/api-documenter": ^7.17.11
|
||||
"@microsoft/api-extractor": ^7.23.0
|
||||
"@microsoft/api-extractor-model": ^7.17.2
|
||||
"@microsoft/tsdoc": 0.14.1
|
||||
"@microsoft/tsdoc-config": 0.16.2
|
||||
"@types/is-glob": ^4.0.2
|
||||
"@types/mock-fs": ^4.13.0
|
||||
chalk: ^4.0.0
|
||||
@@ -8245,6 +8242,9 @@ __metadata:
|
||||
mock-fs: ^5.1.0
|
||||
ts-node: ^10.0.0
|
||||
peerDependencies:
|
||||
"@microsoft/api-extractor-model": "*"
|
||||
"@microsoft/tsdoc": "*"
|
||||
"@microsoft/tsdoc-config": "*"
|
||||
"@rushstack/node-core-library": "*"
|
||||
prettier: ^2.8.1
|
||||
typescript: "> 3.0.0"
|
||||
@@ -9019,20 +9019,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/eslintrc@npm:^1.3.3":
|
||||
version: 1.3.3
|
||||
resolution: "@eslint/eslintrc@npm:1.3.3"
|
||||
"@eslint/eslintrc@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "@eslint/eslintrc@npm:1.4.0"
|
||||
dependencies:
|
||||
ajv: ^6.12.4
|
||||
debug: ^4.3.2
|
||||
espree: ^9.4.0
|
||||
globals: ^13.15.0
|
||||
globals: ^13.19.0
|
||||
ignore: ^5.2.0
|
||||
import-fresh: ^3.2.1
|
||||
js-yaml: ^4.1.0
|
||||
minimatch: ^3.1.2
|
||||
strip-json-comments: ^3.1.1
|
||||
checksum: f03e9d6727efd3e0719da2051ea80c0c73d20e28c171121527dbb868cd34232ca9c1d0525a66e517a404afea26624b1e47895b6a92474678418c2f50c9566694
|
||||
checksum: 73e39c833deafde8d8706e6fa9b52b6d99927c094ead8e405ea4174e8197ec24aac9ba88ae38cc8ad32eaccf07b9c7fc5dc70761d1fba6da41a928691447305f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9853,14 +9853,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@humanwhocodes/config-array@npm:^0.11.6":
|
||||
version: 0.11.7
|
||||
resolution: "@humanwhocodes/config-array@npm:0.11.7"
|
||||
"@humanwhocodes/config-array@npm:^0.11.8":
|
||||
version: 0.11.8
|
||||
resolution: "@humanwhocodes/config-array@npm:0.11.8"
|
||||
dependencies:
|
||||
"@humanwhocodes/object-schema": ^1.2.1
|
||||
debug: ^4.1.1
|
||||
minimatch: ^3.0.5
|
||||
checksum: cf506dc45d9488af7fbf108ea6ac2151ba1a25e6d2b94b9b4fc36d2c1e4099b89ff560296dbfa13947e44604d4ca4a90d97a4fb167370bf8dd01a6ca2b6d83ac
|
||||
checksum: 0fd6b3c54f1674ce0a224df09b9c2f9846d20b9e54fabae1281ecfc04f2e6ad69bf19e1d6af6a28f88e8aa3990168b6cb9e1ef755868c3256a630605ec2cb1d3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -11235,7 +11235,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/api-extractor-model@npm:7.17.2, @microsoft/api-extractor-model@npm:^7.17.2":
|
||||
"@microsoft/api-extractor-model@npm:7.17.2":
|
||||
version: 7.17.2
|
||||
resolution: "@microsoft/api-extractor-model@npm:7.17.2"
|
||||
dependencies:
|
||||
@@ -11275,7 +11275,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/tsdoc-config@npm:0.16.2, @microsoft/tsdoc-config@npm:~0.16.1":
|
||||
"@microsoft/tsdoc-config@npm:~0.16.1":
|
||||
version: 0.16.2
|
||||
resolution: "@microsoft/tsdoc-config@npm:0.16.2"
|
||||
dependencies:
|
||||
@@ -12470,8 +12470,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@rjsf/utils@npm:^5.0.0-beta.14":
|
||||
version: 5.0.0-beta.14
|
||||
resolution: "@rjsf/utils@npm:5.0.0-beta.14"
|
||||
version: 5.0.0-beta.15
|
||||
resolution: "@rjsf/utils@npm:5.0.0-beta.15"
|
||||
dependencies:
|
||||
json-schema-merge-allof: ^0.8.1
|
||||
jsonpointer: ^5.0.1
|
||||
@@ -12480,20 +12480,20 @@ __metadata:
|
||||
react-is: ^18.2.0
|
||||
peerDependencies:
|
||||
react: ^16.14.0 || >=17
|
||||
checksum: 66d29377c98f6e778ec0b167734a949528b0ca2bfb4776dea74fd70add1fb339e14ec878b9162a152e83804bc8094c3ebcd9ebf8b1b68a63c6ee2bf4548938c8
|
||||
checksum: e22b1cc13183d6a6a57b900bb80825eff1c4680edadb554adb6737c21237c7862b2a249e5b67c0c6971094b5d21224daf108ad1f400286be924bbc5c29784a00
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rjsf/validator-ajv6@npm:^5.0.0-beta.14":
|
||||
version: 5.0.0-beta.14
|
||||
resolution: "@rjsf/validator-ajv6@npm:5.0.0-beta.14"
|
||||
version: 5.0.0-beta.15
|
||||
resolution: "@rjsf/validator-ajv6@npm:5.0.0-beta.15"
|
||||
dependencies:
|
||||
ajv: ^6.7.0
|
||||
lodash: ^4.17.15
|
||||
lodash-es: ^4.17.15
|
||||
peerDependencies:
|
||||
"@rjsf/utils": ^5.0.0-beta.1
|
||||
checksum: 57b5c6968475a66762f644f2832aad077f8dde0ca9e71c8b0b27c06174855b7e6330f44fa6078fc0f347b728a4a5fd62399378fad26ada3cde32da89bddf6f9f
|
||||
checksum: ce6d2cd110eee21e37ae08e4081b0f0fd65ac22f8c94ceeaff9f9a9482072897c3f988de1bc770c25031932117e7f71dda0bd166c2bb953af420d0ce1fd255d0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -17866,14 +17866,14 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"canvas@npm:^2.10.2":
|
||||
version: 2.10.2
|
||||
resolution: "canvas@npm:2.10.2"
|
||||
version: 2.11.0
|
||||
resolution: "canvas@npm:2.11.0"
|
||||
dependencies:
|
||||
"@mapbox/node-pre-gyp": ^1.0.0
|
||||
nan: ^2.17.0
|
||||
node-gyp: latest
|
||||
simple-get: ^3.0.3
|
||||
checksum: b2e3eb4c3635fa2f67857619621c3d314f935a9e51904536dadf4908ab580dff4f5bcbaafe6eb0255247fa027ca494d5cd97c33376a49a0f994997263fa9944b
|
||||
checksum: a69a6e0c90014a1b02e52c4c38a627d1a01ffe9539047bec84105cb3554907a947cf39b4a333be43fc1583dd142b641bb5480a4e23f59c6098618c33bf78f67f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -21670,11 +21670,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"eslint@npm:^8.6.0":
|
||||
version: 8.29.0
|
||||
resolution: "eslint@npm:8.29.0"
|
||||
version: 8.30.0
|
||||
resolution: "eslint@npm:8.30.0"
|
||||
dependencies:
|
||||
"@eslint/eslintrc": ^1.3.3
|
||||
"@humanwhocodes/config-array": ^0.11.6
|
||||
"@eslint/eslintrc": ^1.4.0
|
||||
"@humanwhocodes/config-array": ^0.11.8
|
||||
"@humanwhocodes/module-importer": ^1.0.1
|
||||
"@nodelib/fs.walk": ^1.2.8
|
||||
ajv: ^6.10.0
|
||||
@@ -21693,7 +21693,7 @@ __metadata:
|
||||
file-entry-cache: ^6.0.1
|
||||
find-up: ^5.0.0
|
||||
glob-parent: ^6.0.2
|
||||
globals: ^13.15.0
|
||||
globals: ^13.19.0
|
||||
grapheme-splitter: ^1.0.4
|
||||
ignore: ^5.2.0
|
||||
import-fresh: ^3.0.0
|
||||
@@ -21714,7 +21714,7 @@ __metadata:
|
||||
text-table: ^0.2.0
|
||||
bin:
|
||||
eslint: bin/eslint.js
|
||||
checksum: e05204b05907b82d910983995cb946e0ba62ca514eb2b6791c43f623333b143564a2eee0139909d31c10935c21877d815b1f76dd674a59cb91c471064325c4ab
|
||||
checksum: b7525bb465b342665c3b8bab7e114d514ef1bc4e79f211c919863f9c71767e7412ec82383a22614a92d159783f91101018817000f7c61ce69a5e7015280cafaf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -23516,12 +23516,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"globals@npm:^13.15.0":
|
||||
version: 13.15.0
|
||||
resolution: "globals@npm:13.15.0"
|
||||
"globals@npm:^13.19.0":
|
||||
version: 13.19.0
|
||||
resolution: "globals@npm:13.19.0"
|
||||
dependencies:
|
||||
type-fest: ^0.20.2
|
||||
checksum: 383ade0873b2ab29ce6d143466c203ed960491575bc97406395e5c8434026fb02472ab2dfff5bc16689b8460269b18fda1047975295cd0183904385c51258bae
|
||||
checksum: a000dbd00bcf28f0941d8a29c3522b1c3b8e4bfe4e60e262c477a550c3cbbe8dbe2925a6905f037acd40f9a93c039242e1f7079c76b0fd184bc41dcc3b5c8e2e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user