Merge branch 'master' into visibility
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Added `who-am-i` action to the auth backend actions registry. Returns the catalog entity and user info for the currently authenticated user.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/repo-tools': patch
|
||||
---
|
||||
|
||||
Fixed `generate-catalog-info` command failing with "too many arguments" when invoked by lint-staged via the pre-commit hook.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Fixed neutral-1 hover & pressed state in light mode.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-backend': patch
|
||||
'@backstage/plugin-techdocs-node': patch
|
||||
---
|
||||
|
||||
Added `techdocs.generator.mkdocs.dangerouslyAllowAdditionalKeys` configuration option to explicitly bypass MkDocs configuration key restrictions. This enables support for additional MkDocs configuration keys beyond the default safe allow list, such as the `hooks` key which some MkDocs plugins require.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Removed check for deprecated `bitbucket` integration from `repoPickerValidation` function used by the `RepoUrlPicker`, it now validates the `bitbucketServer` and `bitbucketCloud` integrations instead.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-defaults': minor
|
||||
---
|
||||
|
||||
The scheduler service now uses the metrics service to create metrics, providing plugin-scoped attribution.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/repo-tools': minor
|
||||
---
|
||||
|
||||
Added support for OpenAPI 3.1 to all `schema openapi` commands. The commands now auto-detect the OpenAPI version from the spec file and use the appropriate generator, supporting both OpenAPI 3.0.x and 3.1.x specifications.
|
||||
@@ -2,4 +2,4 @@
|
||||
'@backstage/plugin-catalog': minor
|
||||
---
|
||||
|
||||
Added support for group alias IDs and configurable content ordering on the entity page. Groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `contentOrder` option (default `title`) controls how content items within each group are sorted, with support for both a page-level default and per-group overrides.
|
||||
Added support for group alias IDs and configurable content ordering on the entity page. Groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `defaultContentOrder` option (default `title`) controls how content items within each group are sorted, with support for both a page-level default and per-group overrides.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Removed `@backstage/plugin-scaffolder-backend-module-bitbucket` from `package.json` as the package itself has been deprecated and the code deleted.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Fixed tab `matchStrategy` matching to ignore query parameters and hash fragments in tab `href` values. Previously, tabs with query params in their `href` (e.g., `/page?group=foo`) would never show as active since matching compared the full `href` string against `location.pathname` which never includes query params.
|
||||
|
||||
**Affected components:** Tabs, PluginHeader
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-test-utils': patch
|
||||
---
|
||||
|
||||
Fixed type inference of `TestApiPair` when using tuple syntax by wrapping `MockWithApiFactory` in `NoInfer`.
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
'@backstage/backend-dynamic-feature-service': minor
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
'@backstage/catalog-client': minor
|
||||
'@backstage/plugin-catalog-backend': minor
|
||||
'@backstage/plugin-events-backend': minor
|
||||
'@backstage/plugin-search-backend': minor
|
||||
---
|
||||
|
||||
Migrated OpenAPI schemas to 3.1.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Deprecated two processors as they have been moved to the Community Plugins repo with their own backend modules:
|
||||
|
||||
- `AnnotateScmSlugEntityProcessor`: Use `@backstage-community/plugin-catalog-backend-module-annotate-scm-slug` instead
|
||||
- `CodeOwnersProcessor`: Use `@backstage-community/plugin-catalog-backend-module-codeowners` instead
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
'@backstage/integration': major
|
||||
---
|
||||
|
||||
**BREAKING** Removed deprecated Azure DevOps, Bitbucket, Gerrit and GitHub code:
|
||||
|
||||
- For Azure DevOps, the long deprecated `token` string and `credential` object have been removed from the `config.d.ts`. Use the `credentials` array object instead.
|
||||
- For Bitbucket, the long deprecated `bitbucket` object has been removed from the `config.d.ts`. Use the `bitbucketCloud` or `bitbucketServer` objects instead.
|
||||
- For Gerrit, the `parseGerritGitilesUrl` function has been removed, use `parseGitilesUrlRef` instead. The `buildGerritGitilesArchiveUrl` function has also been removed, use `buildGerritGitilesArchiveUrlFromLocation` instead.
|
||||
- For GitHub, the `getGitHubRequestOptions` function has been removed.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-node': minor
|
||||
---
|
||||
|
||||
**BREAKING** Removed deprecated `bitbucket` integration from being used in the `parseRepoUrl` function. It will use the `bitbucketCloud` or `bitbucketServer` integrations instead.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-defaults': minor
|
||||
---
|
||||
|
||||
**BREAKING** Removed deprecated `BitbucketUrlReader`. Use the `BitbucketCloudUrlReader` or the `BitbucketServerUrlReader` instead.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-common': major
|
||||
---
|
||||
|
||||
**BREAKING** Removed deprecated `bitbucket` integration from being registered in the `ScaffolderClient`. Use the `bitbucketCloud` or `bitbucketServer` integrations instead.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
create scaffolder MCP action to dry run a provided scaffolder template
|
||||
@@ -0,0 +1 @@
|
||||
Fixed a type inference issue with the `apis` option of testing utilities in `@backstage/frontend-test-utils`.
|
||||
@@ -283,6 +283,7 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling.
|
||||
| Carlos Esteban Lopez Jaramillo | VMWare | [luchillo17](https://github.com/luchillo17) | `luchillo17#8777` |
|
||||
| David Tuite | Roadie.io | [dtuite](https://github.com/dtuite) | `David Tuite (roadie.io)#1010` |
|
||||
| Deepankumar Loganathan | | [deepan10](https://github.com/deepan10) | `deepan10` |
|
||||
| Elaine Mattos | DB Systel | [elaine-mattos](https://github.com/elaine-mattos) | `elaine_mattos` |
|
||||
| Gabriel Dugny | Believe | [GabDug](https://github.com/GabDug) | `GabDug` |
|
||||
| Heikki Hellgren | OP Financial Group | [drodil](https://github.com/drodil) | `deathammer` |
|
||||
| Himanshu Mishra | Harness.io | [OrkoHunter](https://github.com/OrkoHunter) | `OrkoHunter#1520` |
|
||||
|
||||
@@ -1340,105 +1340,3 @@ const backend = createBackend();
|
||||
backend.add(import('@backstage/plugin-kubernetes-backend'));
|
||||
/* highlight-add-end */
|
||||
```
|
||||
|
||||
### The Plugins in Backstage Repo
|
||||
|
||||
The vast majority of the backend plugins that currently live in the Backstage Repo have been migrated and their respective `README`s have details on how they should be installed using the New Backend System.
|
||||
|
||||
| Package | Role | Migrated | Uses Alpha Export | Link to `README` |
|
||||
| ------------------------------------------------------------------ | --------------------- | -------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| @backstage-community/plugin-adr-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/adr/plugins/adr-backend/README.md) |
|
||||
| @backstage-community/plugin-airbrake-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/airbrake/plugins/airbrake-backend/README.md) |
|
||||
| @backstage/plugin-app-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/app-backend/README.md) |
|
||||
| @backstage/plugin-auth-backend | backend-plugin | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-atlassian-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-atlassian-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-aws-alb-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-aws-alb-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-gcp-iap-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-gcp-iap-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-github-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-github-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-gitlab-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-gitlab-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-google-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-google-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-guest-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-guest-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-microsoft-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-microsoft-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-oauth2-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-oauth2-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-oauth2-proxy-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-oauth2-proxy-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-oidc-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-oidc-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-okta-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-okta-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-pinniped-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-pinniped-provider/README.md) |
|
||||
| @backstage/plugin-auth-backend-module-vmware-cloud-provider | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-vmware-cloud-provider/README.md) |
|
||||
| @backstage-community/plugin-azure-devops-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/azure-devops/plugins/azure-devops-backend/README.md) |
|
||||
| @backstage-community/plugin-azure-sites-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/azure-sites/plugins/azure-sites-backend/README.md) |
|
||||
| @backstage-community/plugin-badges-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/badges/plugins/badges-backend/README.md) |
|
||||
| @backstage-community/plugin-bazaar-backend | backend-plugin | true | true | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/bazaar/plugins/bazaar-backend/README.md) |
|
||||
| @backstage/plugin-catalog-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-aws | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-aws/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-azure | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-azure/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-backstage-openapi | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-backstage-openapi/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-bitbucket-cloud | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-bitbucket-cloud/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-bitbucket-server | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-bitbucket-server/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-gcp | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-gcp/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-gerrit | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-gerrit/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-github | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-github/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-github-org | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-github-org/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-gitlab | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-gitlab/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-incremental-ingestion | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-incremental-ingestion/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-ldap | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-msgraph | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-openapi | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-openapi/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-puppetdb | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-puppetdb/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-scaffolder-entity-model | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-scaffolder-entity-model/README.md) |
|
||||
| @backstage/plugin-catalog-backend-module-unprocessed | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-unprocessed/README.md) |
|
||||
| @backstage-community/plugin-code-coverage-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/code-coverage/plugins/code-coverage-backend/README.md) |
|
||||
| @backstage/plugin-devtools-backend | backend-plugin | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/devtools-backend/README.md) |
|
||||
| @backstage-community/plugin-entity-feedback-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/entity-feedback/plugins/entity-feedback-backend/README.md) |
|
||||
| @backstage/plugin-events-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend/README.md) |
|
||||
| @backstage/plugin-events-backend-module-aws-sqs | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-aws-sqs/README.md) |
|
||||
| @backstage/plugin-events-backend-module-azure | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-azure/README.md) |
|
||||
| @backstage/plugin-events-backend-module-bitbucket-cloud | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-bitbucket-cloud/README.md) |
|
||||
| @backstage/plugin-events-backend-module-gerrit | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gerrit/README.md) |
|
||||
| @backstage/plugin-events-backend-module-github | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-github/README.md) |
|
||||
| @backstage/plugin-events-backend-module-gitlab | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md) |
|
||||
| @internal/plugin-todo-list-backend | backend-plugin | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-backend/README.md) |
|
||||
| @backstage-community/plugin-explore-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/explore/plugins/explore-backend/README.md) |
|
||||
| @backstage-community/plugin-jenkins-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/jenkins/plugins/jenkins-backend/README.md) |
|
||||
| @backstage-community/plugin-kafka-backend | backend-plugin | true | true | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/kafka/plugins/kafka-backend/README.md) |
|
||||
| @backstage/plugin-kubernetes-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/kubernetes-backend/README.md) |
|
||||
| @backstage-community/plugin-lighthouse-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/lighthouse/plugins/lighthouse-backend/README.md) |
|
||||
| @backstage-community/plugin-linguist-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/linguist/plugins/linguist-backend/README.md) |
|
||||
| @backstage-community/plugin-nomad-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/nomad/plugins/nomad-backend/README.md) |
|
||||
| @backstage/plugin-notifications-backend | backend-plugin | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/notifications-backend/README.md) |
|
||||
| @backstage-community/plugin-periskop-backend | backend-plugin | true | true | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/periskop/plugins/periskop-backend/README.md) |
|
||||
| @backstage/plugin-permission-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/permission-backend/README.md) |
|
||||
| @backstage/plugin-permission-backend-module-allow-all-policy | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/permission-backend-module-policy-allow-all/README.md) |
|
||||
| @backstage-community/plugin-playlist-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/playlist/plugins/playlist-backend/README.md) |
|
||||
| @backstage/plugin-proxy-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/proxy-backend/README.md) |
|
||||
| @backstage-community/plugin-rollbar-backend | backend-plugin | | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/rollbar/plugins/rollbar-backend/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-azure | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-azure/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-bitbucket | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-bitbucket/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-bitbucket-cloud | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-bitbucket-cloud/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-bitbucket-server | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-bitbucket-server/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-confluence-to-markdown | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-confluence-to-markdown/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-cookiecutter | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-cookiecutter/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-gerrit | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-gerrit/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-gitea | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-gitea/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-github | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-gitlab | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-gitlab/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-rails | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-rails/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-sentry | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-sentry/README.md) |
|
||||
| @backstage/plugin-scaffolder-backend-module-yeoman | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-yeoman/README.md) |
|
||||
| @backstage/plugin-search-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend/README.md) |
|
||||
| @backstage/plugin-search-backend-module-catalog | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-catalog/README.md) |
|
||||
| @backstage/plugin-search-backend-module-elasticsearch | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-elasticsearch/README.md) |
|
||||
| @backstage/plugin-search-backend-module-explore | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-explore/README.md) |
|
||||
| @backstage/plugin-search-backend-module-pg | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-pg/README.md) |
|
||||
| @backstage/plugin-search-backend-module-stack-overflow-collator | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-stack-overflow-collator/README.md) |
|
||||
| @backstage/plugin-search-backend-module-techdocs | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-techdocs/README.md) |
|
||||
| @backstage/plugin-signals-backend | backend-plugin | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/signals-backend/README.md) |
|
||||
| @backstage-community/plugin-sonarqube-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/sonarqube/plugins/sonarqube-backend/README.md) |
|
||||
| @backstage-community/plugin-stack-overflow-backend | backend-plugin | | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/stack-overflow/plugins/stack-overflow-backend/README.md) |
|
||||
| @backstage-community/plugin-tech-insights-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/tech-insights/plugins/tech-insights-backend/README.md) |
|
||||
| @backstage-community/plugin-tech-insights-backend-module-jsonfc | backend-plugin-module | true | | [README](https://github.com/backstage/community-plugins/blob/main/workspaces/tech-insights/plugins/tech-insights-backend-module-jsonfc/README.md) |
|
||||
| @backstage/plugin-techdocs-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/README.md) |
|
||||
| @backstage-community/plugin-todo-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/todo/plugins/todo-backend/README.md) |
|
||||
| @backstage/plugin-user-settings-backend | backend-plugin | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/user-settings-backend/README.md) |
|
||||
| @backstage-community/plugin-vault-backend | backend-plugin | true | | [README](https://github.com/backstage/community-plugins/blob/master/workspaces/vault/plugins/vault-backend/README.md) |
|
||||
|
||||
@@ -730,12 +730,12 @@ Notes:
|
||||
|
||||
### Content ordering within groups
|
||||
|
||||
By default, content items within each group are sorted alphabetically by title. You can change this with the `contentOrder` option, which supports two modes:
|
||||
By default, content items within each group are sorted alphabetically by title. You can change this with the `defaultContentOrder` option, which supports two modes:
|
||||
|
||||
- **`title`** (default) — sort alphabetically by the content extension's title (case-insensitive).
|
||||
- **`natural`** — preserve the natural extension discovery/registration order.
|
||||
|
||||
A page-level `contentOrder` sets the default for all groups, and individual groups can override it:
|
||||
A page-level `defaultContentOrder` sets the default for all groups, and individual groups can override it with a per-group `contentOrder`:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
@@ -743,7 +743,7 @@ app:
|
||||
- page:catalog/entity:
|
||||
config:
|
||||
# Default content order for all groups
|
||||
contentOrder: title
|
||||
defaultContentOrder: title
|
||||
|
||||
groups:
|
||||
- documentation:
|
||||
|
||||
@@ -607,7 +607,7 @@ component, but there will always be one ultimate owner.
|
||||
|
||||
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
|
||||
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
|
||||
### `spec.system` [optional]
|
||||
|
||||
@@ -811,7 +811,7 @@ Template, but there will always be one ultimate owner.
|
||||
|
||||
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
|
||||
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
|
||||
## Kind: API
|
||||
|
||||
@@ -921,7 +921,7 @@ one ultimate owner.
|
||||
|
||||
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
|
||||
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
|
||||
### `spec.system` [optional]
|
||||
|
||||
@@ -1164,7 +1164,7 @@ resource, but there will always be one ultimate owner.
|
||||
|
||||
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
|
||||
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
|
||||
### `spec.type` [required]
|
||||
|
||||
@@ -1262,7 +1262,7 @@ but there will always be one ultimate owner.
|
||||
|
||||
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
|
||||
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
|
||||
### `spec.domain` [optional]
|
||||
|
||||
@@ -1334,7 +1334,7 @@ but there will always be one ultimate owner.
|
||||
|
||||
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
|
||||
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownedBy`, and reverse `ownerOf`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
|
||||
### `spec.subdomainOf` [optional]
|
||||
|
||||
|
||||
@@ -26,7 +26,14 @@ This tutorial assumes that you're already familiar with the following,
|
||||
|
||||
1. How to build a Backstage plugin.
|
||||
2. `Express.js` and `Typescript`
|
||||
3. OpenAPI 3.0 schemas
|
||||
3. OpenAPI 3.1 schemas
|
||||
|
||||
:::note OpenAPI Version Support
|
||||
Backstage supports both OpenAPI 3.0 and 3.1 specifications. If you have existing OpenAPI 3.0 specs, we recommend that you migrate them to 3.1. The main changes are:
|
||||
|
||||
- Replace `nullable: true` with `type: ['string', 'null']` or use `anyOf`/`oneOf`
|
||||
- Remove `allowReserved` from path parameters (only valid on query/cookie parameters in 3.1)
|
||||
:::
|
||||
|
||||
### Setting up
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ app:
|
||||
config:
|
||||
showNavItemIcons: true
|
||||
# default content order for all groups, can be 'title' or 'natural'
|
||||
# contentOrder: title
|
||||
# defaultContentOrder: title
|
||||
groups:
|
||||
# placing a tab at the beginning
|
||||
- overview:
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { DatabaseService } from '@backstage/backend-plugin-api';
|
||||
import { HttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { MetricsService } from '@backstage/backend-plugin-api/alpha';
|
||||
import { PluginMetadataService } from '@backstage/backend-plugin-api';
|
||||
import { RootLifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api';
|
||||
@@ -17,6 +18,7 @@ export class DefaultSchedulerService {
|
||||
static create(options: {
|
||||
database: DatabaseService;
|
||||
logger: LoggerService;
|
||||
metrics: MetricsService;
|
||||
rootLifecycle: RootLifecycleService;
|
||||
httpRouter: HttpRouterService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
|
||||
@@ -10,7 +10,6 @@ import { AzureCredentialsManager } from '@backstage/integration';
|
||||
import { AzureDevOpsCredentialsProvider } from '@backstage/integration';
|
||||
import { AzureIntegration } from '@backstage/integration';
|
||||
import { BitbucketCloudIntegration } from '@backstage/integration';
|
||||
import { BitbucketIntegration } from '@backstage/integration';
|
||||
import { BitbucketServerIntegration } from '@backstage/integration';
|
||||
import { Config } from '@backstage/config';
|
||||
import { GerritIntegration } from '@backstage/integration';
|
||||
@@ -190,38 +189,6 @@ export class BitbucketServerUrlReader implements UrlReaderService {
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
// @public @deprecated
|
||||
export class BitbucketUrlReader implements UrlReaderService {
|
||||
constructor(
|
||||
integration: BitbucketIntegration,
|
||||
logger: LoggerService,
|
||||
deps: {
|
||||
treeResponseFactory: ReadTreeResponseFactory;
|
||||
},
|
||||
);
|
||||
// (undocumented)
|
||||
static factory: ReaderFactory;
|
||||
// (undocumented)
|
||||
read(url: string): Promise<Buffer>;
|
||||
// (undocumented)
|
||||
readTree(
|
||||
url: string,
|
||||
options?: UrlReaderServiceReadTreeOptions,
|
||||
): Promise<UrlReaderServiceReadTreeResponse>;
|
||||
// (undocumented)
|
||||
readUrl(
|
||||
url: string,
|
||||
options?: UrlReaderServiceReadUrlOptions,
|
||||
): Promise<UrlReaderServiceReadUrlResponse>;
|
||||
// (undocumented)
|
||||
search(
|
||||
url: string,
|
||||
options?: UrlReaderServiceSearchOptions,
|
||||
): Promise<UrlReaderServiceSearchResponse>;
|
||||
// (undocumented)
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class FetchUrlReader implements UrlReaderService {
|
||||
static factory: ReaderFactory;
|
||||
|
||||
+4
@@ -20,6 +20,7 @@ import waitForExpect from 'wait-for-expect';
|
||||
import { DefaultSchedulerService } from './DefaultSchedulerService';
|
||||
import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal';
|
||||
import { PluginMetadataService } from '@backstage/backend-plugin-api';
|
||||
import { metricsServiceMock } from '@backstage/backend-test-utils/alpha';
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
@@ -32,6 +33,7 @@ describe('TaskScheduler', () => {
|
||||
getId: () => 'test',
|
||||
} satisfies PluginMetadataService;
|
||||
const testScopedSignal = createTestScopedSignal();
|
||||
const metrics = metricsServiceMock.mock();
|
||||
|
||||
it.each(databases.eachSupportedId())(
|
||||
'can return a working v1 plugin impl, %p',
|
||||
@@ -42,6 +44,7 @@ describe('TaskScheduler', () => {
|
||||
const manager = DefaultSchedulerService.create({
|
||||
database,
|
||||
logger,
|
||||
metrics,
|
||||
rootLifecycle,
|
||||
httpRouter,
|
||||
pluginMetadata,
|
||||
@@ -71,6 +74,7 @@ describe('TaskScheduler', () => {
|
||||
const manager = DefaultSchedulerService.create({
|
||||
database,
|
||||
logger,
|
||||
metrics,
|
||||
rootLifecycle,
|
||||
httpRouter,
|
||||
pluginMetadata,
|
||||
|
||||
@@ -27,6 +27,7 @@ import { Duration } from 'luxon';
|
||||
import { migrateBackendTasks } from '../database/migrateBackendTasks';
|
||||
import { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl';
|
||||
import { PluginTaskSchedulerJanitor } from './PluginTaskSchedulerJanitor';
|
||||
import { MetricsService } from '@backstage/backend-plugin-api/alpha';
|
||||
|
||||
/**
|
||||
* Default implementation of the task scheduler service.
|
||||
@@ -37,6 +38,7 @@ export class DefaultSchedulerService {
|
||||
static create(options: {
|
||||
database: DatabaseService;
|
||||
logger: LoggerService;
|
||||
metrics: MetricsService;
|
||||
rootLifecycle: RootLifecycleService;
|
||||
httpRouter: HttpRouterService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
@@ -67,6 +69,7 @@ export class DefaultSchedulerService {
|
||||
options.pluginMetadata.getId(),
|
||||
databaseFactory,
|
||||
options.logger,
|
||||
options.metrics,
|
||||
options.rootLifecycle,
|
||||
);
|
||||
|
||||
|
||||
+2
@@ -27,6 +27,7 @@ import {
|
||||
parseDuration,
|
||||
} from './PluginTaskSchedulerImpl';
|
||||
import { createDeferred } from '@backstage/types';
|
||||
import { metricsServiceMock } from '@backstage/backend-test-utils/alpha';
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
@@ -56,6 +57,7 @@ describe('PluginTaskManagerImpl', () => {
|
||||
'myplugin',
|
||||
async () => knex,
|
||||
mockServices.logger.mock(),
|
||||
metricsServiceMock.mock(),
|
||||
{
|
||||
addShutdownHook,
|
||||
addBeforeShutdownHook: jest.fn(),
|
||||
|
||||
+22
-13
@@ -24,7 +24,13 @@ import {
|
||||
SchedulerServiceTaskRunner,
|
||||
SchedulerServiceTaskScheduleDefinition,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { Counter, Histogram, Gauge, metrics, trace } from '@opentelemetry/api';
|
||||
import { trace } from '@opentelemetry/api';
|
||||
import {
|
||||
MetricsService,
|
||||
MetricsServiceCounter,
|
||||
MetricsServiceGauge,
|
||||
MetricsServiceHistogram,
|
||||
} from '@backstage/backend-plugin-api/alpha';
|
||||
import { Knex } from 'knex';
|
||||
import { Duration } from 'luxon';
|
||||
import express from 'express';
|
||||
@@ -45,10 +51,10 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
|
||||
private readonly allScheduledTasks: SchedulerServiceTaskDescriptor[] = [];
|
||||
private readonly shutdownInitiated: Promise<boolean>;
|
||||
|
||||
private readonly counter: Counter;
|
||||
private readonly duration: Histogram;
|
||||
private readonly lastStarted: Gauge;
|
||||
private readonly lastCompleted: Gauge;
|
||||
private readonly counter: MetricsServiceCounter;
|
||||
private readonly duration: MetricsServiceHistogram;
|
||||
private readonly lastStarted: MetricsServiceGauge;
|
||||
private readonly lastCompleted: MetricsServiceGauge;
|
||||
|
||||
private readonly pluginId: string;
|
||||
private readonly databaseFactory: () => Promise<Knex>;
|
||||
@@ -58,24 +64,27 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
|
||||
pluginId: string,
|
||||
databaseFactory: () => Promise<Knex>,
|
||||
logger: LoggerService,
|
||||
metrics: MetricsService,
|
||||
rootLifecycle: RootLifecycleService,
|
||||
) {
|
||||
this.pluginId = pluginId;
|
||||
this.databaseFactory = databaseFactory;
|
||||
this.logger = logger;
|
||||
const meter = metrics.getMeter('default');
|
||||
this.counter = meter.createCounter('backend_tasks.task.runs.count', {
|
||||
this.counter = metrics.createCounter('backend_tasks.task.runs.count', {
|
||||
description: 'Total number of times a task has been run',
|
||||
});
|
||||
this.duration = meter.createHistogram('backend_tasks.task.runs.duration', {
|
||||
description: 'Histogram of task run durations',
|
||||
unit: 'seconds',
|
||||
});
|
||||
this.lastStarted = meter.createGauge('backend_tasks.task.runs.started', {
|
||||
this.duration = metrics.createHistogram(
|
||||
'backend_tasks.task.runs.duration',
|
||||
{
|
||||
description: 'Histogram of task run durations',
|
||||
unit: 'seconds',
|
||||
},
|
||||
);
|
||||
this.lastStarted = metrics.createGauge('backend_tasks.task.runs.started', {
|
||||
description: 'Epoch timestamp seconds when the task was last started',
|
||||
unit: 'seconds',
|
||||
});
|
||||
this.lastCompleted = meter.createGauge(
|
||||
this.lastCompleted = metrics.createGauge(
|
||||
'backend_tasks.task.runs.completed',
|
||||
{
|
||||
description: 'Epoch timestamp seconds when the task was last completed',
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { metricsServiceRef } from '@backstage/backend-plugin-api/alpha';
|
||||
import { DefaultSchedulerService } from './lib/DefaultSchedulerService';
|
||||
|
||||
/**
|
||||
@@ -37,6 +38,7 @@ export const schedulerServiceFactory = createServiceFactory({
|
||||
rootLifecycle: coreServices.rootLifecycle,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
pluginMetadata: coreServices.pluginMetadata,
|
||||
metrics: metricsServiceRef,
|
||||
},
|
||||
async factory({
|
||||
database,
|
||||
@@ -44,6 +46,7 @@ export const schedulerServiceFactory = createServiceFactory({
|
||||
rootLifecycle,
|
||||
httpRouter,
|
||||
pluginMetadata,
|
||||
metrics,
|
||||
}) {
|
||||
return DefaultSchedulerService.create({
|
||||
database,
|
||||
@@ -51,6 +54,7 @@ export const schedulerServiceFactory = createServiceFactory({
|
||||
rootLifecycle,
|
||||
httpRouter,
|
||||
pluginMetadata,
|
||||
metrics,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,656 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
BitbucketIntegration,
|
||||
readBitbucketIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
createMockDirectory,
|
||||
mockServices,
|
||||
registerMswTestHooks,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import fs from 'fs-extra';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import path from 'node:path';
|
||||
import { NotModifiedError } from '@backstage/errors';
|
||||
import { BitbucketUrlReader } from './BitbucketUrlReader';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
import getRawBody from 'raw-body';
|
||||
import { UrlReaderServiceReadUrlResponse } from '@backstage/backend-plugin-api';
|
||||
|
||||
const logger = mockServices.logger.mock();
|
||||
|
||||
describe('BitbucketUrlReader.factory', () => {
|
||||
it('only apply integration configs not inherited from bitbucketCloud or bitbucketServer', () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
bitbucket: [],
|
||||
bitbucketCloud: [
|
||||
{
|
||||
username: 'username',
|
||||
appPassword: 'password',
|
||||
},
|
||||
],
|
||||
bitbucketServer: [
|
||||
{
|
||||
host: 'bitbucket-server.local',
|
||||
token: 'test-token',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: config,
|
||||
});
|
||||
|
||||
const tuples = BitbucketUrlReader.factory({
|
||||
config,
|
||||
logger,
|
||||
treeResponseFactory,
|
||||
});
|
||||
|
||||
expect(tuples).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BitbucketUrlReader', () => {
|
||||
const mockDir = createMockDirectory({ mockOsTmpDir: true });
|
||||
|
||||
beforeEach(mockDir.clear);
|
||||
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
const bitbucketProcessor = new BitbucketUrlReader(
|
||||
new BitbucketIntegration(
|
||||
readBitbucketIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'bitbucket.org',
|
||||
apiBaseUrl: 'https://api.bitbucket.org/2.0',
|
||||
}),
|
||||
),
|
||||
),
|
||||
logger,
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
|
||||
const hostedBitbucketProcessor = new BitbucketUrlReader(
|
||||
new BitbucketIntegration(
|
||||
readBitbucketIntegrationConfig(
|
||||
new ConfigReader({
|
||||
host: 'bitbucket.mycompany.net',
|
||||
apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0',
|
||||
}),
|
||||
),
|
||||
),
|
||||
logger,
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
|
||||
const worker = setupServer();
|
||||
registerMswTestHooks(worker);
|
||||
|
||||
describe('readUrl', () => {
|
||||
it('should be able to readUrl via buffer without ETag', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('If-None-Match')).toBeNull();
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.body('foo'),
|
||||
ctx.set('ETag', 'etag-value'),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await bitbucketProcessor.readUrl(
|
||||
'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
|
||||
);
|
||||
const buffer = await result.buffer();
|
||||
expect(buffer.toString()).toBe('foo');
|
||||
});
|
||||
|
||||
it('should be able to readUrl using provided token', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('authorization')).toBe(
|
||||
'Bearer manual-token',
|
||||
);
|
||||
return res(ctx.status(200), ctx.body('foo'));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await bitbucketProcessor.readUrl(
|
||||
'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
|
||||
{ token: 'manual-token' },
|
||||
);
|
||||
const buffer = await result.buffer();
|
||||
expect(buffer.toString()).toBe('foo');
|
||||
});
|
||||
|
||||
it('should be able to readUrl via stream without ETag', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('If-None-Match')).toBeNull();
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.body('foo'),
|
||||
ctx.set('ETag', 'etag-value'),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await bitbucketProcessor.readUrl(
|
||||
'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
|
||||
);
|
||||
const fromStream = await getRawBody(result.stream!());
|
||||
expect(fromStream.toString()).toBe('foo');
|
||||
});
|
||||
|
||||
it('should be able to readUrl with matching ETag', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('If-None-Match')).toBe(
|
||||
'matching-etag-value',
|
||||
);
|
||||
return res(ctx.status(304));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await expect(
|
||||
bitbucketProcessor.readUrl(
|
||||
'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
|
||||
{ etag: 'matching-etag-value' },
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
|
||||
it('should be able to readUrl without matching ETag', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('If-None-Match')).toBe(
|
||||
'previous-etag-value',
|
||||
);
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.body('foo'),
|
||||
ctx.set('ETag', 'new-etag-value'),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await bitbucketProcessor.readUrl(
|
||||
'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
|
||||
{ etag: 'previous-etag-value' },
|
||||
);
|
||||
const buffer = await result.buffer();
|
||||
expect(buffer.toString()).toBe('foo');
|
||||
expect(result.etag).toBe('new-etag-value');
|
||||
});
|
||||
|
||||
it('should be able to readUrl via buffer without If-Modified-Since', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('If-None-Match')).toBeNull();
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.body('foo'),
|
||||
ctx.set('ETag', 'etag-value'),
|
||||
ctx.set(
|
||||
'Last-Modified',
|
||||
new Date('2020-01-01T00:00:00Z').toUTCString(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await bitbucketProcessor.readUrl(
|
||||
'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
|
||||
);
|
||||
const buffer = await result.buffer();
|
||||
expect(result.lastModifiedAt).toEqual(new Date('2020-01-01T00:00:00Z'));
|
||||
expect(buffer.toString()).toBe('foo');
|
||||
});
|
||||
|
||||
it('should be throw not modified when If-Modified-Since returns a 304', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('If-Modified-Since')).toBe(
|
||||
new Date('1999 12 31 23:59:59 GMT').toUTCString(),
|
||||
);
|
||||
return res(ctx.status(304));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await expect(
|
||||
bitbucketProcessor.readUrl(
|
||||
'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
|
||||
{ lastModifiedAfter: new Date('1999 12 31 23:59:59 GMT') },
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
|
||||
it('should be able to readUrl when If-Modified-Since is before Last-Modified', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('If-Modified-Since')).toBe(
|
||||
new Date('1999 12 31 23:59:59 GMT').toUTCString(),
|
||||
);
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.set(
|
||||
'Last-Modified',
|
||||
new Date('2020-01-01T00:00:00Z').toUTCString(),
|
||||
),
|
||||
ctx.body('foo'),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await bitbucketProcessor.readUrl(
|
||||
'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml',
|
||||
{ lastModifiedAfter: new Date('1999 12 31 23:59:59 GMT') },
|
||||
);
|
||||
const buffer = await result.buffer();
|
||||
expect(buffer.toString()).toBe('foo');
|
||||
expect(result.lastModifiedAt).toEqual(new Date('2020-01-01T00:00:00Z'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('read', () => {
|
||||
it('rejects unknown targets', async () => {
|
||||
await expect(
|
||||
bitbucketProcessor.read('https://not.bitbucket.com/apa'),
|
||||
).rejects.toThrow(
|
||||
'Incorrect URL: https://not.bitbucket.com/apa, Error: Invalid Bitbucket URL or file path',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readTree', () => {
|
||||
const repoBuffer = fs.readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'__fixtures__/bitbucket-repo-with-commit-hash.tar.gz',
|
||||
),
|
||||
);
|
||||
|
||||
const privateBitbucketRepoBuffer = fs.readFileSync(
|
||||
path.resolve(__dirname, '__fixtures__/bitbucket-server-repo.tar.gz'),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage/mock',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
mainbranch: {
|
||||
type: 'branch',
|
||||
name: 'master',
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://bitbucket.org/backstage/mock/get/master.tar.gz',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/zip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=backstage-mock-12ab34cd56ef.tar.gz',
|
||||
),
|
||||
ctx.body(new Uint8Array(repoBuffer)),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage/mock/commits/master',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
values: [{ hash: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }],
|
||||
}),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/zip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=backstage-mock.tgz',
|
||||
),
|
||||
ctx.body(new Uint8Array(privateBitbucketRepoBuffer)),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
values: [{ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }],
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the wanted files from an archive', async () => {
|
||||
const response = await bitbucketProcessor.readTree(
|
||||
'https://bitbucket.org/backstage/mock/src/master',
|
||||
);
|
||||
|
||||
expect(response.etag).toBe('12ab34cd56ef');
|
||||
|
||||
const files = await response.files();
|
||||
|
||||
expect(files.length).toBe(2);
|
||||
const mkDocsFile = await files[0].content();
|
||||
const indexMarkdownFile = await files[1].content();
|
||||
|
||||
expect(indexMarkdownFile.toString()).toBe('# Test\n');
|
||||
expect(mkDocsFile.toString()).toBe('site_name: Test\n');
|
||||
});
|
||||
|
||||
it('creates a directory with the wanted files', async () => {
|
||||
const response = await bitbucketProcessor.readTree(
|
||||
'https://bitbucket.org/backstage/mock',
|
||||
);
|
||||
|
||||
const dir = await response.dir({ targetDir: mockDir.path });
|
||||
|
||||
await expect(
|
||||
fs.readFile(path.join(dir, 'mkdocs.yml'), 'utf8'),
|
||||
).resolves.toBe('site_name: Test\n');
|
||||
await expect(
|
||||
fs.readFile(path.join(dir, 'docs', 'index.md'), 'utf8'),
|
||||
).resolves.toBe('# Test\n');
|
||||
});
|
||||
|
||||
it('uses private bitbucket host', async () => {
|
||||
const response = await hostedBitbucketProcessor.readTree(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs?at=some-branch',
|
||||
);
|
||||
|
||||
expect(response.etag).toBe('12ab34cd56ef');
|
||||
|
||||
const files = await response.files();
|
||||
|
||||
expect(files.length).toBe(1);
|
||||
const indexMarkdownFile = await files[0].content();
|
||||
|
||||
expect(indexMarkdownFile.toString()).toBe('# Test\n');
|
||||
});
|
||||
|
||||
it('returns the wanted files from an archive with a subpath', async () => {
|
||||
const response = await bitbucketProcessor.readTree(
|
||||
'https://bitbucket.org/backstage/mock/src/master/docs',
|
||||
);
|
||||
|
||||
expect(response.etag).toBe('12ab34cd56ef');
|
||||
|
||||
const files = await response.files();
|
||||
|
||||
expect(files.length).toBe(1);
|
||||
const indexMarkdownFile = await files[0].content();
|
||||
|
||||
expect(indexMarkdownFile.toString()).toBe('# Test\n');
|
||||
});
|
||||
|
||||
it('creates a directory with the wanted files with a subpath', async () => {
|
||||
const response = await bitbucketProcessor.readTree(
|
||||
'https://bitbucket.org/backstage/mock/src/master/docs',
|
||||
);
|
||||
|
||||
const dir = await response.dir({ targetDir: mockDir.path });
|
||||
|
||||
await expect(
|
||||
fs.readFile(path.join(dir, 'index.md'), 'utf8'),
|
||||
).resolves.toBe('# Test\n');
|
||||
});
|
||||
|
||||
it('throws a NotModifiedError when given a etag in options', async () => {
|
||||
const fnBitbucket = async () => {
|
||||
await bitbucketProcessor.readTree(
|
||||
'https://bitbucket.org/backstage/mock',
|
||||
{ etag: '12ab34cd56ef' },
|
||||
);
|
||||
};
|
||||
|
||||
await expect(fnBitbucket).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
|
||||
it('should not throw a NotModifiedError when given an outdated etag in options', async () => {
|
||||
const response = await bitbucketProcessor.readTree(
|
||||
'https://bitbucket.org/backstage/mock',
|
||||
{ etag: 'outdatedetag123abc' },
|
||||
);
|
||||
|
||||
expect(response.etag).toBe('12ab34cd56ef');
|
||||
});
|
||||
});
|
||||
|
||||
describe('search hosted', () => {
|
||||
const repoBuffer = fs.readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'__fixtures__/bitbucket-repo-with-commit-hash.tar.gz',
|
||||
),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage/mock',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
mainbranch: {
|
||||
type: 'branch',
|
||||
name: 'master',
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://bitbucket.org/backstage/mock/get/master.tar.gz',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/zip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=backstage-mock-12ab34cd56ef.tar.gz',
|
||||
),
|
||||
ctx.body(new Uint8Array(repoBuffer)),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.bitbucket.org/2.0/repositories/backstage/mock/commits/master',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
values: [{ hash: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }],
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('works for the naive case', async () => {
|
||||
const result = await bitbucketProcessor.search(
|
||||
'https://bitbucket.org/backstage/mock/src/master/**/index.*',
|
||||
);
|
||||
expect(result.etag).toBe('12ab34cd56ef');
|
||||
expect(result.files.length).toBe(1);
|
||||
expect(result.files[0].url).toBe(
|
||||
'https://bitbucket.org/backstage/mock/src/master/docs/index.md',
|
||||
);
|
||||
await expect(result.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('# Test\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('works in nested folders', async () => {
|
||||
const result = await bitbucketProcessor.search(
|
||||
'https://bitbucket.org/backstage/mock/src/master/docs/index.*',
|
||||
);
|
||||
expect(result.etag).toBe('12ab34cd56ef');
|
||||
expect(result.files.length).toBe(1);
|
||||
expect(result.files[0].url).toBe(
|
||||
'https://bitbucket.org/backstage/mock/src/master/docs/index.md',
|
||||
);
|
||||
await expect(result.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('# Test\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws NotModifiedError when same etag', async () => {
|
||||
await expect(
|
||||
bitbucketProcessor.search(
|
||||
'https://bitbucket.org/backstage/mock/src/master/**/index.*',
|
||||
{ etag: '12ab34cd56ef' },
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('search private', () => {
|
||||
const privateBitbucketRepoBuffer = fs.readFileSync(
|
||||
path.resolve(__dirname, '__fixtures__/bitbucket-server-repo.tar.gz'),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/zip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=backstage-mock.tgz',
|
||||
),
|
||||
ctx.body(new Uint8Array(privateBitbucketRepoBuffer)),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
values: [{ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }],
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('works for the naive case', async () => {
|
||||
const result = await hostedBitbucketProcessor.search(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/**/index.*?at=master',
|
||||
);
|
||||
expect(result.etag).toBe('12ab34cd56ef');
|
||||
expect(result.files.length).toBe(1);
|
||||
expect(result.files[0].url).toBe(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.md?at=master',
|
||||
);
|
||||
await expect(result.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('# Test\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('works in nested folders', async () => {
|
||||
const result = await hostedBitbucketProcessor.search(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.*?at=master',
|
||||
);
|
||||
expect(result.etag).toBe('12ab34cd56ef');
|
||||
expect(result.files.length).toBe(1);
|
||||
expect(result.files[0].url).toBe(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.md?at=master',
|
||||
);
|
||||
await expect(result.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('# Test\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('throws NotModifiedError when same etag', async () => {
|
||||
await expect(
|
||||
hostedBitbucketProcessor.search(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/**/index.*?at=master',
|
||||
{ etag: '12ab34cd56ef' },
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
|
||||
it('should work for exact URLs', async () => {
|
||||
hostedBitbucketProcessor.readUrl = jest.fn().mockResolvedValue({
|
||||
buffer: async () => Buffer.from('content'),
|
||||
etag: 'etag',
|
||||
} as UrlReaderServiceReadUrlResponse);
|
||||
|
||||
const result = await hostedBitbucketProcessor.search(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.md?at=master',
|
||||
);
|
||||
expect(result.etag).toBe('etag');
|
||||
expect(result.files.length).toBe(1);
|
||||
expect(result.files[0].url).toBe(
|
||||
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs/index.md?at=master',
|
||||
);
|
||||
expect((await result.files[0].content()).toString()).toEqual('content');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,313 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
import {
|
||||
UrlReaderService,
|
||||
UrlReaderServiceReadTreeOptions,
|
||||
UrlReaderServiceReadTreeResponse,
|
||||
UrlReaderServiceReadUrlOptions,
|
||||
UrlReaderServiceReadUrlResponse,
|
||||
UrlReaderServiceSearchOptions,
|
||||
UrlReaderServiceSearchResponse,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
assertError,
|
||||
NotFoundError,
|
||||
NotModifiedError,
|
||||
} from '@backstage/errors';
|
||||
import {
|
||||
BitbucketIntegration,
|
||||
getBitbucketDefaultBranch,
|
||||
getBitbucketDownloadUrl,
|
||||
getBitbucketFileFetchUrl,
|
||||
getBitbucketRequestOptions,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { trimEnd } from 'lodash';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { ReaderFactory, ReadTreeResponseFactory } from './types';
|
||||
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
|
||||
/**
|
||||
* Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket v1 and v2 APIs, such
|
||||
* as the one exposed by Bitbucket Cloud itself.
|
||||
*
|
||||
* @public
|
||||
* @deprecated in favor of BitbucketCloudUrlReader and BitbucketServerUrlReader
|
||||
*/
|
||||
export class BitbucketUrlReader implements UrlReaderService {
|
||||
static factory: ReaderFactory = ({ config, logger, treeResponseFactory }) => {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
return integrations.bitbucket
|
||||
.list()
|
||||
.filter(
|
||||
item =>
|
||||
!integrations.bitbucketCloud.byHost(item.config.host) &&
|
||||
!integrations.bitbucketServer.byHost(item.config.host),
|
||||
)
|
||||
.map(integration => {
|
||||
const reader = new BitbucketUrlReader(integration, logger, {
|
||||
treeResponseFactory,
|
||||
});
|
||||
const predicate = (url: URL) => url.host === integration.config.host;
|
||||
return { reader, predicate };
|
||||
});
|
||||
};
|
||||
|
||||
private readonly integration: BitbucketIntegration;
|
||||
private readonly deps: { treeResponseFactory: ReadTreeResponseFactory };
|
||||
|
||||
constructor(
|
||||
integration: BitbucketIntegration,
|
||||
logger: LoggerService,
|
||||
deps: { treeResponseFactory: ReadTreeResponseFactory },
|
||||
) {
|
||||
this.integration = integration;
|
||||
this.deps = deps;
|
||||
const { host, token, username, appPassword } = integration.config;
|
||||
const replacement =
|
||||
host === 'bitbucket.org' ? 'bitbucketCloud' : 'bitbucketServer';
|
||||
logger.warn(
|
||||
`[Deprecated] Please migrate from "integrations.bitbucket" to "integrations.${replacement}".`,
|
||||
);
|
||||
|
||||
if (!token && username && !appPassword) {
|
||||
throw new Error(
|
||||
`Bitbucket integration for '${host}' has configured a username but is missing a required appPassword.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async read(url: string): Promise<Buffer> {
|
||||
const response = await this.readUrl(url);
|
||||
return response.buffer();
|
||||
}
|
||||
|
||||
private getCredentials = async (options?: {
|
||||
token?: string;
|
||||
}): Promise<{ headers: Record<string, string> }> => {
|
||||
if (options?.token) {
|
||||
return {
|
||||
headers: {
|
||||
Authorization: `Bearer ${options.token}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return await getBitbucketRequestOptions(this.integration.config);
|
||||
};
|
||||
|
||||
async readUrl(
|
||||
url: string,
|
||||
options?: UrlReaderServiceReadUrlOptions,
|
||||
): Promise<UrlReaderServiceReadUrlResponse> {
|
||||
const { etag, lastModifiedAfter, signal } = options ?? {};
|
||||
const bitbucketUrl = getBitbucketFileFetchUrl(url, this.integration.config);
|
||||
const requestOptions = await this.getCredentials(options);
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(bitbucketUrl.toString(), {
|
||||
headers: {
|
||||
...requestOptions.headers,
|
||||
...(etag && { 'If-None-Match': etag }),
|
||||
...(lastModifiedAfter && {
|
||||
'If-Modified-Since': lastModifiedAfter.toUTCString(),
|
||||
}),
|
||||
},
|
||||
// TODO(freben): The signal cast is there because pre-3.x versions of
|
||||
// node-fetch have a very slightly deviating AbortSignal type signature.
|
||||
// The difference does not affect us in practice however. The cast can be
|
||||
// removed after we support ESM for CLI dependencies and migrate to
|
||||
// version 3 of node-fetch.
|
||||
// https://github.com/backstage/backstage/issues/8242
|
||||
...(signal && { signal: signal as any }),
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to read ${url}, ${e}`);
|
||||
}
|
||||
|
||||
if (response.status === 304) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
return ReadUrlResponseFactory.fromResponse(response);
|
||||
}
|
||||
|
||||
const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`;
|
||||
if (response.status === 404) {
|
||||
throw new NotFoundError(message);
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
async readTree(
|
||||
url: string,
|
||||
options?: UrlReaderServiceReadTreeOptions,
|
||||
): Promise<UrlReaderServiceReadTreeResponse> {
|
||||
const { filepath } = parseGitUrl(url);
|
||||
|
||||
const lastCommitShortHash = await this.getLastCommitShortHash(url);
|
||||
if (options?.etag && options.etag === lastCommitShortHash) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
const downloadUrl = await getBitbucketDownloadUrl(
|
||||
url,
|
||||
this.integration.config,
|
||||
);
|
||||
const archiveBitbucketResponse = await fetch(
|
||||
downloadUrl,
|
||||
getBitbucketRequestOptions(this.integration.config),
|
||||
);
|
||||
if (!archiveBitbucketResponse.ok) {
|
||||
const message = `Failed to read tree from ${url}, ${archiveBitbucketResponse.status} ${archiveBitbucketResponse.statusText}`;
|
||||
if (archiveBitbucketResponse.status === 404) {
|
||||
throw new NotFoundError(message);
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
response: archiveBitbucketResponse,
|
||||
subpath: filepath,
|
||||
etag: lastCommitShortHash,
|
||||
filter: options?.filter,
|
||||
});
|
||||
}
|
||||
|
||||
async search(
|
||||
url: string,
|
||||
options?: UrlReaderServiceSearchOptions,
|
||||
): Promise<UrlReaderServiceSearchResponse> {
|
||||
const { filepath } = parseGitUrl(url);
|
||||
|
||||
// If it's a direct URL we use readUrl instead
|
||||
if (!filepath?.match(/[*?]/)) {
|
||||
try {
|
||||
const data = await this.readUrl(url, options);
|
||||
|
||||
return {
|
||||
files: [
|
||||
{
|
||||
url: url,
|
||||
content: data.buffer,
|
||||
lastModifiedAt: data.lastModifiedAt,
|
||||
},
|
||||
],
|
||||
etag: data.etag ?? '',
|
||||
};
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
if (error.name === 'NotFoundError') {
|
||||
return {
|
||||
files: [],
|
||||
etag: '',
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const matcher = new Minimatch(filepath);
|
||||
|
||||
// TODO(freben): For now, read the entire repo and filter through that. In
|
||||
// a future improvement, we could be smart and try to deduce that non-glob
|
||||
// prefixes (like for filepaths such as some-prefix/**/a.yaml) can be used
|
||||
// to get just that part of the repo.
|
||||
const treeUrl = trimEnd(url.replace(filepath, ''), '/');
|
||||
|
||||
const tree = await this.readTree(treeUrl, {
|
||||
etag: options?.etag,
|
||||
filter: path => matcher.match(path),
|
||||
});
|
||||
const files = await tree.files();
|
||||
|
||||
return {
|
||||
etag: tree.etag,
|
||||
files: files.map(file => ({
|
||||
url: this.integration.resolveUrl({
|
||||
url: `/${file.path}`,
|
||||
base: url,
|
||||
}),
|
||||
content: file.content,
|
||||
lastModifiedAt: file.lastModifiedAt,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host, token, username, appPassword } = this.integration.config;
|
||||
let authed = Boolean(token);
|
||||
if (!authed) {
|
||||
authed = Boolean(username && appPassword);
|
||||
}
|
||||
return `bitbucket{host=${host},authed=${authed}}`;
|
||||
}
|
||||
|
||||
private async getLastCommitShortHash(url: string): Promise<string> {
|
||||
const { resource, name: repoName, owner: project, ref } = parseGitUrl(url);
|
||||
|
||||
let branch = ref;
|
||||
if (!branch) {
|
||||
branch = await getBitbucketDefaultBranch(url, this.integration.config);
|
||||
}
|
||||
|
||||
const isHosted = resource === 'bitbucket.org';
|
||||
// Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp222
|
||||
const commitsApiUrl = isHosted
|
||||
? `${this.integration.config.apiBaseUrl}/repositories/${project}/${repoName}/commits/${branch}`
|
||||
: `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/commits`;
|
||||
|
||||
const commitsResponse = await fetch(
|
||||
commitsApiUrl,
|
||||
getBitbucketRequestOptions(this.integration.config),
|
||||
);
|
||||
if (!commitsResponse.ok) {
|
||||
const message = `Failed to retrieve commits from ${commitsApiUrl}, ${commitsResponse.status} ${commitsResponse.statusText}`;
|
||||
if (commitsResponse.status === 404) {
|
||||
throw new NotFoundError(message);
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const commits = await commitsResponse.json();
|
||||
if (isHosted) {
|
||||
if (
|
||||
commits &&
|
||||
commits.values &&
|
||||
commits.values.length > 0 &&
|
||||
commits.values[0].hash
|
||||
) {
|
||||
return commits.values[0].hash.substring(0, 12);
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
commits &&
|
||||
commits.values &&
|
||||
commits.values.length > 0 &&
|
||||
commits.values[0].id
|
||||
) {
|
||||
return commits.values[0].id.substring(0, 12);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Failed to read response from ${commitsApiUrl}`);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import { UrlReaderPredicateMux } from './UrlReaderPredicateMux';
|
||||
import { AzureUrlReader } from './AzureUrlReader';
|
||||
import { BitbucketCloudUrlReader } from './BitbucketCloudUrlReader';
|
||||
import { BitbucketServerUrlReader } from './BitbucketServerUrlReader';
|
||||
import { BitbucketUrlReader } from './BitbucketUrlReader';
|
||||
import { GerritUrlReader } from './GerritUrlReader';
|
||||
import { GithubUrlReader } from './GithubUrlReader';
|
||||
import { GitlabUrlReader } from './GitlabUrlReader';
|
||||
@@ -92,7 +91,6 @@ export class UrlReaders {
|
||||
AzureUrlReader.factory,
|
||||
BitbucketCloudUrlReader.factory,
|
||||
BitbucketServerUrlReader.factory,
|
||||
BitbucketUrlReader.factory,
|
||||
GerritUrlReader.factory,
|
||||
GithubUrlReader.factory,
|
||||
GiteaUrlReader.factory,
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
export { AzureUrlReader } from './AzureUrlReader';
|
||||
export { BitbucketCloudUrlReader } from './BitbucketCloudUrlReader';
|
||||
export { BitbucketUrlReader } from './BitbucketUrlReader';
|
||||
export { BitbucketServerUrlReader } from './BitbucketServerUrlReader';
|
||||
export { GerritUrlReader } from './GerritUrlReader';
|
||||
export { GithubUrlReader } from './GithubUrlReader';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
openapi: 3.0.3
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: .backstage/dynamic-features
|
||||
version: '1'
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
@@ -26,7 +26,6 @@ import { ErrorResponse } from '../models/ErrorResponse.model';
|
||||
*/
|
||||
export interface ModelError {
|
||||
[key: string]: any;
|
||||
|
||||
error: ErrorError;
|
||||
request?: ErrorRequest;
|
||||
response: ErrorResponse;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
@@ -21,7 +21,7 @@ import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage
|
||||
import { EndpointMap } from './apis';
|
||||
|
||||
export const spec = {
|
||||
openapi: '3.0.3',
|
||||
openapi: '3.1.0',
|
||||
info: {
|
||||
title: '.backstage/dynamic-features',
|
||||
version: '1',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
This package is meant to provide a typed Express router for an OpenAPI spec. Based on the [`oatx`](https://github.com/varanauskas/oatx) library and adapted to override Express values.
|
||||
|
||||
Only supports OpenAPI 3.1 specifications.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Configuration
|
||||
|
||||
@@ -54,6 +54,7 @@ export function getOpenApiSpecRoute(baseUrl: string) {
|
||||
/**
|
||||
* Create a router with validation middleware. This is used by typing methods to create an
|
||||
* "OpenAPI router" with all of the expected validation + metadata.
|
||||
* Only supports OpenAPI 3.1 specifications.
|
||||
* @param spec - Your OpenAPI spec imported as a JSON object.
|
||||
* @param validatorOptions - `openapi-express-validator` options to override the defaults.
|
||||
* @returns A new express router with validation middleware.
|
||||
@@ -115,6 +116,7 @@ function createRouterWithValidation(
|
||||
|
||||
/**
|
||||
* Create a new OpenAPI router with some default middleware.
|
||||
* Only supports OpenAPI 3.1 specifications.
|
||||
* @param spec - Your OpenAPI spec imported as a JSON object.
|
||||
* @param validatorOptions - `openapi-express-validator` options to override the defaults.
|
||||
* @returns A new express router with validation middleware.
|
||||
@@ -132,6 +134,7 @@ export function createValidatedOpenApiRouter<T extends RequiredDoc>(
|
||||
|
||||
/**
|
||||
* Create a new OpenAPI router with some default middleware.
|
||||
* Only supports OpenAPI 3.1 specifications.
|
||||
* @param spec - Your OpenAPI spec imported as a JSON object.
|
||||
* @param validatorOptions - `openapi-express-validator` options to override the defaults.
|
||||
* @returns A new express router with validation middleware.
|
||||
|
||||
@@ -260,9 +260,9 @@ export class DefaultApiClient {
|
||||
|
||||
/**
|
||||
* Get all entities matching a given filter.
|
||||
* @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it'll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name`
|
||||
* @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it\'ll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name`
|
||||
* @param limit - Number of records to return in the response.
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let\'s look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
* @param offset - Number of records to skip in the query page.
|
||||
* @param after - Pointer to the previous page of results.
|
||||
* @param order -
|
||||
@@ -291,12 +291,12 @@ export class DefaultApiClient {
|
||||
|
||||
/**
|
||||
* Search for entities by a given query.
|
||||
* @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it'll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name`
|
||||
* @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it\'ll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name`
|
||||
* @param limit - Number of records to return in the response.
|
||||
* @param offset - Number of records to skip in the query page.
|
||||
* @param orderField - By default the entities are returned ordered by their internal uid. You can customize the `orderField` query parameters to affect that ordering. For example, to return entities by their name: `/entities/by-query?orderField=metadata.name,asc` Each parameter can be followed by `asc` for ascending lexicographical order or `desc` for descending (reverse) lexicographical order.
|
||||
* @param cursor - You may pass the `cursor` query parameters to perform cursor based pagination through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property: ```json \"pageInfo\": { \"nextCursor\": \"a-cursor\", \"prevCursor\": \"another-cursor\" } ``` If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach, if `prevCursor` exists, it can be used to retrieve the previous batch of entities. - [`filter`](#filtering), for selecting only a subset of all entities - [`fields`](#field-selection), for selecting only parts of the full data structure of each entity - `limit` for limiting the number of entities returned (20 is the default) - [`orderField`](#ordering), for deciding the order of the entities - `fullTextFilter` **NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that, it isn't possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters, as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration.
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
* @param cursor - You may pass the `cursor` query parameters to perform cursor based pagination through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property: ```json \"pageInfo\": { \"nextCursor\": \"a-cursor\", \"prevCursor\": \"another-cursor\" } ``` If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach, if `prevCursor` exists, it can be used to retrieve the previous batch of entities. - [`filter`](#filtering), for selecting only a subset of all entities - [`fields`](#field-selection), for selecting only parts of the full data structure of each entity - `limit` for limiting the number of entities returned (20 is the default) - [`orderField`](#ordering), for deciding the order of the entities - `fullTextFilter` **NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that, it isn\'t possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters, as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration.
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let\'s look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
* @param fullTextFilterTerm - Text search term.
|
||||
* @param fullTextFilterFields - A comma separated list of fields to sort returned results by.
|
||||
*/
|
||||
@@ -324,7 +324,7 @@ export class DefaultApiClient {
|
||||
|
||||
/**
|
||||
* Get a batch set of entities given an array of entityRefs.
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let\'s look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
* @param getEntitiesByRefsRequest -
|
||||
*/
|
||||
public async getEntitiesByRefs(
|
||||
@@ -351,7 +351,7 @@ export class DefaultApiClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity's ancestry by entity ref.
|
||||
* Get an entity\'s ancestry by entity ref.
|
||||
* @param kind -
|
||||
* @param namespace -
|
||||
* @param name -
|
||||
@@ -439,7 +439,7 @@ export class DefaultApiClient {
|
||||
/**
|
||||
* Get all entity facets that match the given filters.
|
||||
* @param facet -
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let\'s look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
*/
|
||||
public async getEntityFacets(
|
||||
// @ts-ignore
|
||||
|
||||
+1
-2
@@ -17,12 +17,11 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { Entity } from '../models/Entity.model';
|
||||
import { LocationSpec } from '../models/LocationSpec.model';
|
||||
|
||||
/**
|
||||
* If the folder pointed to already contained catalog info yaml files, they are read and emitted like this so that the frontend can inform the user that it located them and can make sure to register them as well if they weren't already
|
||||
* If the folder pointed to already contained catalog info yaml files, they are read and emitted like this so that the frontend can inform the user that it located them and can make sure to register them as well if they weren\'t already
|
||||
* @public
|
||||
*/
|
||||
export interface AnalyzeLocationExistingEntity {
|
||||
|
||||
+1
-2
@@ -17,12 +17,11 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { AnalyzeLocationEntityField } from '../models/AnalyzeLocationEntityField.model';
|
||||
import { RecursivePartialEntity } from '../models/RecursivePartialEntity.model';
|
||||
|
||||
/**
|
||||
* This is some form of representation of what the analyzer could deduce. We should probably have a chat about how this can best be conveyed to the frontend. It'll probably contain a (possibly incomplete) entity, plus enough info for the frontend to know what form data to show to the user for overriding/completing the info.
|
||||
* This is some form of representation of what the analyzer could deduce. We should probably have a chat about how this can best be conveyed to the frontend. It\'ll probably contain a (possibly incomplete) entity, plus enough info for the frontend to know what form data to show to the user for overriding/completing the info.
|
||||
* @public
|
||||
*/
|
||||
export interface AnalyzeLocationGenerateEntity {
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { LocationInput } from '../models/LocationInput.model';
|
||||
|
||||
/**
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { AnalyzeLocationExistingEntity } from '../models/AnalyzeLocationExistingEntity.model';
|
||||
import { AnalyzeLocationGenerateEntity } from '../models/AnalyzeLocationGenerateEntity.model';
|
||||
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { Entity } from '../models/Entity.model';
|
||||
import { Location } from '../models/Location.model';
|
||||
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { NullableEntity } from '../models/NullableEntity.model';
|
||||
|
||||
/**
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { EntitiesQueryResponsePageInfo } from '../models/EntitiesQueryResponsePageInfo.model';
|
||||
import { Entity } from '../models/Entity.model';
|
||||
|
||||
|
||||
@@ -17,12 +17,11 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { EntityMeta } from '../models/EntityMeta.model';
|
||||
import { EntityRelation } from '../models/EntityRelation.model';
|
||||
|
||||
/**
|
||||
* The parts of the format that's common to all versions/kinds of entity.
|
||||
* The parts of the format that\'s common to all versions/kinds of entity.
|
||||
* @public
|
||||
*/
|
||||
export interface Entity {
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { EntityAncestryResponseItemsInner } from '../models/EntityAncestryResponseItemsInner.model';
|
||||
|
||||
/**
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { Entity } from '../models/Entity.model';
|
||||
|
||||
/**
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { EntityFacet } from '../models/EntityFacet.model';
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { EntityLink } from '../models/EntityLink.model';
|
||||
|
||||
/**
|
||||
@@ -26,7 +25,6 @@ import { EntityLink } from '../models/EntityLink.model';
|
||||
*/
|
||||
export interface EntityMeta {
|
||||
[key: string]: any;
|
||||
|
||||
/**
|
||||
* A list of external hyperlinks related to the entity.
|
||||
*/
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { Location } from '../models/Location.model';
|
||||
|
||||
/**
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { Location } from '../models/Location.model';
|
||||
import { LocationsQueryResponsePageInfo } from '../models/LocationsQueryResponsePageInfo.model';
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { ErrorError } from '../models/ErrorError.model';
|
||||
import { ErrorRequest } from '../models/ErrorRequest.model';
|
||||
import { ErrorResponse } from '../models/ErrorResponse.model';
|
||||
@@ -27,7 +26,6 @@ import { ErrorResponse } from '../models/ErrorResponse.model';
|
||||
*/
|
||||
export interface ModelError {
|
||||
[key: string]: any;
|
||||
|
||||
error: ErrorError;
|
||||
request?: ErrorRequest;
|
||||
response: ErrorResponse;
|
||||
|
||||
@@ -17,12 +17,11 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { EntityMeta } from '../models/EntityMeta.model';
|
||||
import { EntityRelation } from '../models/EntityRelation.model';
|
||||
|
||||
/**
|
||||
* The parts of the format that's common to all versions/kinds of entity.
|
||||
* The parts of the format that\'s common to all versions/kinds of entity.
|
||||
* @public
|
||||
*/
|
||||
export type NullableEntity = {
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { QueryEntitiesByPredicateRequestFullTextFilter } from '../models/QueryEntitiesByPredicateRequestFullTextFilter.model';
|
||||
import { QueryEntitiesByPredicateRequestOrderByInner } from '../models/QueryEntitiesByPredicateRequestOrderByInner.model';
|
||||
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { RecursivePartialEntityMeta } from '../models/RecursivePartialEntityMeta.model';
|
||||
import { RecursivePartialEntityRelation } from '../models/RecursivePartialEntityRelation.model';
|
||||
|
||||
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { EntityLink } from '../models/EntityLink.model';
|
||||
|
||||
/**
|
||||
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 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.
|
||||
*/
|
||||
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { EntityLink } from '../models/EntityLink.model';
|
||||
|
||||
/**
|
||||
* Metadata fields common to all versions/kinds of entity.
|
||||
* @public
|
||||
*/
|
||||
export interface RecursivePartialEntityMetaAllOf {
|
||||
/**
|
||||
* A list of external hyperlinks related to the entity.
|
||||
*/
|
||||
links?: Array<EntityLink>;
|
||||
/**
|
||||
* A list of single-valued strings, to for example classify catalog entities in various ways.
|
||||
*/
|
||||
tags?: Array<string>;
|
||||
/**
|
||||
* Construct a type with a set of properties K of type T
|
||||
*/
|
||||
annotations?: { [key: string]: string };
|
||||
/**
|
||||
* Construct a type with a set of properties K of type T
|
||||
*/
|
||||
labels?: { [key: string]: string };
|
||||
/**
|
||||
* A short (typically relatively few words, on one line) description of the entity.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* A display name of the entity, to be presented in user interfaces instead of the `name` property above, when available. This field is sometimes useful when the `name` is cumbersome or ends up being perceived as overly technical. The title generally does not have as stringent format requirements on it, so it may contain special characters and be more explanatory. Do keep it very short though, and avoid situations where a title can be confused with the name of another entity, or where two entities share a title. Note that this is only for display purposes, and may be ignored by some parts of the code. Entity references still always make use of the `name` property, not the title.
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* The namespace that the entity belongs to.
|
||||
*/
|
||||
namespace?: string;
|
||||
/**
|
||||
* The name of the entity. Must be unique within the catalog at any given point in time, for any given namespace + kind pair. This value is part of the technical identifier of the entity, and as such it will appear in URLs, database tables, entity references, and similar. It is subject to restrictions regarding what characters are allowed. If you want to use a different, more human readable string with fewer restrictions on it in user interfaces, see the `title` field below.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* An opaque string that changes for each update operation to any part of the entity, including metadata. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, and the server will then reject the operation if it does not match the current stored value.
|
||||
*/
|
||||
etag?: string;
|
||||
/**
|
||||
* A globally unique ID for the entity. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, but the server is free to reject requests that do so in such a way that it breaks semantics.
|
||||
*/
|
||||
uid?: string;
|
||||
}
|
||||
-1
@@ -17,7 +17,6 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { ValidateEntity400ResponseErrorsInner } from '../models/ValidateEntity400ResponseErrorsInner.model';
|
||||
|
||||
/**
|
||||
|
||||
-1
@@ -23,7 +23,6 @@
|
||||
*/
|
||||
export interface ValidateEntity400ResponseErrorsInner {
|
||||
[key: string]: any;
|
||||
|
||||
name: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ export * from '../models/QueryEntitiesByPredicateRequestOrderByInner.model';
|
||||
export * from '../models/QueryEntityFacetsByPredicateRequest.model';
|
||||
export * from '../models/RecursivePartialEntity.model';
|
||||
export * from '../models/RecursivePartialEntityMeta.model';
|
||||
export * from '../models/RecursivePartialEntityMetaAllOf.model';
|
||||
export * from '../models/RecursivePartialEntityRelation.model';
|
||||
export * from '../models/RefreshEntityRequest.model';
|
||||
export * from '../models/ValidateEntity400Response.model';
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
// ******************************************************************
|
||||
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
|
||||
// ******************************************************************
|
||||
|
||||
import { DiscoveryApi } from '../types/discovery';
|
||||
import { FetchApi } from '../types/fetch';
|
||||
import crossFetch from 'cross-fetch';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
@@ -26,7 +26,6 @@ import { ErrorResponse } from '../models/ErrorResponse.model';
|
||||
*/
|
||||
export interface ModelError {
|
||||
[key: string]: any;
|
||||
|
||||
error: ErrorError;
|
||||
request?: ErrorRequest;
|
||||
response: ErrorResponse;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
* Copyright 2026 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.
|
||||
|
||||
@@ -443,7 +443,7 @@ export type RenderTestAppOptions<TApiPairs extends any[] = any[]> = {
|
||||
// @public
|
||||
export type TestApiPair<TApi> =
|
||||
| readonly [ApiRef<TApi>, TApi extends infer TImpl ? Partial<TImpl> : never]
|
||||
| MockWithApiFactory<TApi>;
|
||||
| MockWithApiFactory<NoInfer<TApi>>;
|
||||
|
||||
// @public
|
||||
export type TestApiPairs<TApiPairs> = {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
import { createApiRef } from '@backstage/frontend-plugin-api';
|
||||
import { TestApiProvider } from './TestApiProvider';
|
||||
import { mockApis } from './mockApis';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
const xApiRef = createApiRef<{ a: string; b: number }>({
|
||||
id: 'x',
|
||||
});
|
||||
const yApiRef = createApiRef<string>({
|
||||
id: 'y',
|
||||
});
|
||||
|
||||
describe('TestApiProvider', () => {
|
||||
it('should provide tuple APIs and check types', () => {
|
||||
render(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[xApiRef, { a: 'a', b: 3 }],
|
||||
[yApiRef, 'y'],
|
||||
]}
|
||||
>
|
||||
<div />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow partial API implementations', () => {
|
||||
render(
|
||||
<TestApiProvider apis={[[xApiRef, { a: 'a' }]]}>
|
||||
<div />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject mismatched types in tuple syntax', () => {
|
||||
render(
|
||||
// @ts-expect-error - a should be a string, not a number
|
||||
<TestApiProvider apis={[[xApiRef, { a: 3 }]]}>
|
||||
<div />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should accept MockWithApiFactory entries', () => {
|
||||
render(
|
||||
<TestApiProvider apis={[mockApis.alert()]}>
|
||||
<div />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should accept a mix of tuples and MockWithApiFactory entries', () => {
|
||||
render(
|
||||
<TestApiProvider apis={[[xApiRef, { a: 'a' }], mockApis.alert()]}>
|
||||
<div />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow empty APIs', () => {
|
||||
render(
|
||||
<TestApiProvider apis={[]}>
|
||||
<div />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should provide APIs at runtime', async () => {
|
||||
const alertApi = mockApis.alert();
|
||||
|
||||
render(
|
||||
<TestApiProvider apis={[[xApiRef, { a: 'hello', b: 42 }], alertApi]}>
|
||||
<span>rendered</span>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(await screen.findByText('rendered')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
*/
|
||||
export type TestApiPair<TApi> =
|
||||
| readonly [ApiRef<TApi>, TApi extends infer TImpl ? Partial<TImpl> : never]
|
||||
| MockWithApiFactory<TApi>;
|
||||
| MockWithApiFactory<NoInfer<TApi>>;
|
||||
|
||||
/**
|
||||
* Represents an array of mock API implementation.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { ScmIntegration, ScmIntegrationsGroup } from '@backstage/integration';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { scmIntegrationsApiRef } from '../src/ScmIntegrationsApi';
|
||||
import { scmIntegrationsApiRef } from '../src/api/ScmIntegrationsApi';
|
||||
|
||||
import { Content } from '@backstage/core-components';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
@@ -48,10 +48,6 @@ export const DevPage = () => {
|
||||
Azure
|
||||
</Typography>
|
||||
<Integrations group={integrations.azure} />
|
||||
<Typography paragraph variant="h2">
|
||||
Bitbucket
|
||||
</Typography>
|
||||
<Integrations group={integrations.bitbucket} />
|
||||
<Typography paragraph variant="h2">
|
||||
Bitbucket Cloud
|
||||
</Typography>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { scmIntegrationsApiRef } from '../src/ScmIntegrationsApi';
|
||||
import { scmIntegrationsApiRef } from '../src/api/ScmIntegrationsApi';
|
||||
import { DevPage } from './DevPage';
|
||||
import { configApiRef, createApiFactory } from '@backstage/core-plugin-api';
|
||||
|
||||
|
||||
@@ -26,6 +26,6 @@ describe('scmIntegrationsApiRef', () => {
|
||||
|
||||
it('should be instantiated', () => {
|
||||
const i = ScmIntegrationsApi.fromConfig(new ConfigReader({}));
|
||||
expect(i.list().length).toBe(8); // The default ones
|
||||
expect(i.list().length).toBe(7); // The default ones
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
-58
@@ -27,27 +27,6 @@ export interface Config {
|
||||
* @visibility frontend
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* Token used to authenticate requests.
|
||||
* @visibility secret
|
||||
* @deprecated Use `credentials` instead.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* The credential to use for requests.
|
||||
*
|
||||
* If no credential is specified anonymous access is used.
|
||||
*
|
||||
* @deepVisibility secret
|
||||
* @deprecated Use `credentials` instead.
|
||||
*/
|
||||
credential?: {
|
||||
clientId?: string;
|
||||
clientSecret?: string;
|
||||
tenantId?: string;
|
||||
personalAccessToken?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The credentials to use for requests. If multiple credentials are specified the first one that matches the organization is used.
|
||||
@@ -132,43 +111,6 @@ export interface Config {
|
||||
};
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Integration configuration for Bitbucket
|
||||
* @deprecated replaced by bitbucketCloud and bitbucketServer
|
||||
*/
|
||||
bitbucket?: Array<{
|
||||
/**
|
||||
* The hostname of the given Bitbucket instance
|
||||
* @visibility frontend
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* Token used to authenticate requests.
|
||||
* @visibility secret
|
||||
*/
|
||||
token?: string;
|
||||
/**
|
||||
* The base url for the Bitbucket API, for example https://api.bitbucket.org/2.0
|
||||
* @visibility frontend
|
||||
*/
|
||||
apiBaseUrl?: string;
|
||||
/**
|
||||
* The username to use for authenticated requests.
|
||||
* @visibility secret
|
||||
*/
|
||||
username?: string;
|
||||
/**
|
||||
* Bitbucket app password used to authenticate requests.
|
||||
* @visibility secret
|
||||
*/
|
||||
appPassword?: string;
|
||||
/**
|
||||
* PGP signing key for signing commits.
|
||||
* @visibility secret
|
||||
*/
|
||||
commitSigningKey?: string;
|
||||
}>;
|
||||
|
||||
/** Integration configuration for Bitbucket Cloud */
|
||||
bitbucketCloud?: Array<{
|
||||
/**
|
||||
|
||||
@@ -201,8 +201,6 @@ export class AzureIntegration implements ScmIntegration {
|
||||
// @public
|
||||
export type AzureIntegrationConfig = {
|
||||
host: string;
|
||||
token?: string;
|
||||
credential?: AzureDevOpsCredential;
|
||||
credentials?: AzureDevOpsCredential[];
|
||||
commitSigningKey?: string;
|
||||
};
|
||||
@@ -255,37 +253,6 @@ export type BitbucketCloudIntegrationConfig = {
|
||||
commitSigningKey?: string;
|
||||
};
|
||||
|
||||
// @public @deprecated
|
||||
export class BitbucketIntegration implements ScmIntegration {
|
||||
constructor(integrationConfig: BitbucketIntegrationConfig);
|
||||
// (undocumented)
|
||||
get config(): BitbucketIntegrationConfig;
|
||||
// (undocumented)
|
||||
static factory: ScmIntegrationsFactory<BitbucketIntegration>;
|
||||
// (undocumented)
|
||||
resolveEditUrl(url: string): string;
|
||||
// (undocumented)
|
||||
resolveUrl(options: {
|
||||
url: string;
|
||||
base: string;
|
||||
lineNumber?: number;
|
||||
}): string;
|
||||
// (undocumented)
|
||||
get title(): string;
|
||||
// (undocumented)
|
||||
get type(): string;
|
||||
}
|
||||
|
||||
// @public @deprecated
|
||||
export type BitbucketIntegrationConfig = {
|
||||
host: string;
|
||||
apiBaseUrl: string;
|
||||
token?: string;
|
||||
username?: string;
|
||||
appPassword?: string;
|
||||
commitSigningKey?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class BitbucketServerIntegration implements ScmIntegration {
|
||||
constructor(integrationConfig: BitbucketServerIntegrationConfig);
|
||||
@@ -317,14 +284,6 @@ export type BitbucketServerIntegrationConfig = {
|
||||
commitSigningKey?: string;
|
||||
};
|
||||
|
||||
// @public @deprecated
|
||||
export function buildGerritGitilesArchiveUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
project: string,
|
||||
branch: string,
|
||||
filePath: string,
|
||||
): string;
|
||||
|
||||
// @public
|
||||
export function buildGerritGitilesArchiveUrlFromLocation(
|
||||
config: GerritIntegrationConfig,
|
||||
@@ -426,14 +385,6 @@ export function getAzureDownloadUrl(url: string): string;
|
||||
// @public
|
||||
export function getAzureFileFetchUrl(url: string): string;
|
||||
|
||||
// @public @deprecated
|
||||
export function getAzureRequestOptions(
|
||||
config: AzureIntegrationConfig,
|
||||
additionalHeaders?: Record<string, string>,
|
||||
): Promise<{
|
||||
headers: Record<string, string>;
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export function getBitbucketCloudDefaultBranch(
|
||||
url: string,
|
||||
@@ -465,31 +416,6 @@ export function getBitbucketCloudRequestOptions(
|
||||
headers: Record<string, string>;
|
||||
}>;
|
||||
|
||||
// @public @deprecated
|
||||
export function getBitbucketDefaultBranch(
|
||||
url: string,
|
||||
config: BitbucketIntegrationConfig,
|
||||
): Promise<string>;
|
||||
|
||||
// @public @deprecated
|
||||
export function getBitbucketDownloadUrl(
|
||||
url: string,
|
||||
config: BitbucketIntegrationConfig,
|
||||
): Promise<string>;
|
||||
|
||||
// @public @deprecated
|
||||
export function getBitbucketFileFetchUrl(
|
||||
url: string,
|
||||
config: BitbucketIntegrationConfig,
|
||||
): string;
|
||||
|
||||
// @public @deprecated
|
||||
export function getBitbucketRequestOptions(
|
||||
config: BitbucketIntegrationConfig,
|
||||
): {
|
||||
headers: Record<string, string>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function getBitbucketServerDefaultBranch(
|
||||
url: string,
|
||||
@@ -579,14 +505,6 @@ export function getGithubFileFetchUrl(
|
||||
credentials: GithubCredentials,
|
||||
): string;
|
||||
|
||||
// @public @deprecated
|
||||
export function getGitHubRequestOptions(
|
||||
config: GithubIntegrationConfig,
|
||||
credentials: GithubCredentials,
|
||||
): {
|
||||
headers: Record<string, string>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function getGitilesAuthenticationUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
@@ -854,8 +772,6 @@ export interface IntegrationsByType {
|
||||
azure: ScmIntegrationsGroup<AzureIntegration>;
|
||||
// (undocumented)
|
||||
azureBlobStorage: ScmIntegrationsGroup<AzureBlobStorageIntergation>;
|
||||
// @deprecated (undocumented)
|
||||
bitbucket: ScmIntegrationsGroup<BitbucketIntegration>;
|
||||
// (undocumented)
|
||||
bitbucketCloud: ScmIntegrationsGroup<BitbucketCloudIntegration>;
|
||||
// (undocumented)
|
||||
@@ -874,16 +790,6 @@ export interface IntegrationsByType {
|
||||
harness: ScmIntegrationsGroup<HarnessIntegration>;
|
||||
}
|
||||
|
||||
// @public @deprecated
|
||||
export function parseGerritGitilesUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
url: string,
|
||||
): {
|
||||
branch: string;
|
||||
filePath: string;
|
||||
project: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function parseGerritJsonResponse(response: Response): Promise<unknown>;
|
||||
|
||||
@@ -989,16 +895,6 @@ export function readBitbucketCloudIntegrationConfigs(
|
||||
configs: Config[],
|
||||
): BitbucketCloudIntegrationConfig[];
|
||||
|
||||
// @public @deprecated
|
||||
export function readBitbucketIntegrationConfig(
|
||||
config: Config,
|
||||
): BitbucketIntegrationConfig;
|
||||
|
||||
// @public @deprecated
|
||||
export function readBitbucketIntegrationConfigs(
|
||||
configs: Config[],
|
||||
): BitbucketIntegrationConfig[];
|
||||
|
||||
// @public
|
||||
export function readBitbucketServerIntegrationConfig(
|
||||
config: Config,
|
||||
@@ -1085,8 +981,6 @@ export interface ScmIntegrationRegistry
|
||||
azure: ScmIntegrationsGroup<AzureIntegration>;
|
||||
// (undocumented)
|
||||
azureBlobStorage: ScmIntegrationsGroup<AzureBlobStorageIntergation>;
|
||||
// @deprecated (undocumented)
|
||||
bitbucket: ScmIntegrationsGroup<BitbucketIntegration>;
|
||||
// (undocumented)
|
||||
bitbucketCloud: ScmIntegrationsGroup<BitbucketCloudIntegration>;
|
||||
// (undocumented)
|
||||
@@ -1120,8 +1014,6 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
get azure(): ScmIntegrationsGroup<AzureIntegration>;
|
||||
// (undocumented)
|
||||
get azureBlobStorage(): ScmIntegrationsGroup<AzureBlobStorageIntergation>;
|
||||
// @deprecated (undocumented)
|
||||
get bitbucket(): ScmIntegrationsGroup<BitbucketIntegration>;
|
||||
// (undocumented)
|
||||
get bitbucketCloud(): ScmIntegrationsGroup<BitbucketCloudIntegration>;
|
||||
// (undocumented)
|
||||
|
||||
@@ -21,8 +21,6 @@ import {
|
||||
BitbucketCloudIntegration,
|
||||
BitbucketCloudIntegrationConfig,
|
||||
} from './bitbucketCloud';
|
||||
import { BitbucketIntegrationConfig } from './bitbucket';
|
||||
import { BitbucketIntegration } from './bitbucket/BitbucketIntegration';
|
||||
import {
|
||||
BitbucketServerIntegration,
|
||||
BitbucketServerIntegrationConfig,
|
||||
@@ -62,10 +60,6 @@ describe('ScmIntegrations', () => {
|
||||
host: 'azureblobstorage.local',
|
||||
} as AzureBlobStorageIntegrationConfig);
|
||||
|
||||
const bitbucket = new BitbucketIntegration({
|
||||
host: 'bitbucket.local',
|
||||
} as BitbucketIntegrationConfig);
|
||||
|
||||
const bitbucketCloud = new BitbucketCloudIntegration({
|
||||
host: 'bitbucket.org',
|
||||
} as BitbucketCloudIntegrationConfig);
|
||||
@@ -103,7 +97,6 @@ describe('ScmIntegrations', () => {
|
||||
awsCodeCommit: basicIntegrations([awsCodeCommit], item => item.config.host),
|
||||
azure: basicIntegrations([azure], item => item.config.host),
|
||||
azureBlobStorage: basicIntegrations([azureBlob], item => item.config.host),
|
||||
bitbucket: basicIntegrations([bitbucket], item => item.config.host),
|
||||
bitbucketCloud: basicIntegrations([bitbucketCloud], item => item.title),
|
||||
bitbucketServer: basicIntegrations(
|
||||
[bitbucketServer],
|
||||
@@ -126,7 +119,6 @@ describe('ScmIntegrations', () => {
|
||||
expect(i.azureBlobStorage.byUrl('https://azureblobstorage.local')).toBe(
|
||||
azureBlob,
|
||||
);
|
||||
expect(i.bitbucket.byUrl('https://bitbucket.local')).toBe(bitbucket);
|
||||
expect(i.bitbucketCloud.byUrl('https://bitbucket.org')).toBe(
|
||||
bitbucketCloud,
|
||||
);
|
||||
@@ -147,7 +139,6 @@ describe('ScmIntegrations', () => {
|
||||
awsCodeCommit,
|
||||
azure,
|
||||
azureBlob,
|
||||
bitbucket,
|
||||
bitbucketCloud,
|
||||
bitbucketServer,
|
||||
gerrit,
|
||||
@@ -166,7 +157,6 @@ describe('ScmIntegrations', () => {
|
||||
expect(i.azureBlobStorage.byUrl('https://azureblobstorage.local')).toBe(
|
||||
azureBlob,
|
||||
);
|
||||
expect(i.byUrl('https://bitbucket.local')).toBe(bitbucket);
|
||||
expect(i.byUrl('https://bitbucket.org')).toBe(bitbucketCloud);
|
||||
expect(i.byUrl('https://bitbucket-server.local')).toBe(bitbucketServer);
|
||||
expect(i.byUrl('https://gerrit.local')).toBe(gerrit);
|
||||
@@ -179,7 +169,6 @@ describe('ScmIntegrations', () => {
|
||||
expect(i.byHost('awscodecommit.local')).toBe(awsCodeCommit);
|
||||
expect(i.byHost('azure.local')).toBe(azure);
|
||||
expect(i.byHost('azureblobstorage.local')).toBe(azureBlob);
|
||||
expect(i.byHost('bitbucket.local')).toBe(bitbucket);
|
||||
expect(i.byHost('bitbucket.org')).toBe(bitbucketCloud);
|
||||
expect(i.byHost('bitbucket-server.local')).toBe(bitbucketServer);
|
||||
expect(i.byHost('gerrit.local')).toBe(gerrit);
|
||||
|
||||
@@ -19,7 +19,6 @@ import { AwsS3Integration } from './awsS3/AwsS3Integration';
|
||||
import { AwsCodeCommitIntegration } from './awsCodeCommit/AwsCodeCommitIntegration';
|
||||
import { AzureIntegration } from './azure/AzureIntegration';
|
||||
import { BitbucketCloudIntegration } from './bitbucketCloud/BitbucketCloudIntegration';
|
||||
import { BitbucketIntegration } from './bitbucket/BitbucketIntegration';
|
||||
import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerIntegration';
|
||||
import { GerritIntegration } from './gerrit/GerritIntegration';
|
||||
import { GithubIntegration } from './github/GithubIntegration';
|
||||
@@ -42,10 +41,6 @@ export interface IntegrationsByType {
|
||||
awsCodeCommit: ScmIntegrationsGroup<AwsCodeCommitIntegration>;
|
||||
azureBlobStorage: ScmIntegrationsGroup<AzureBlobStorageIntergation>;
|
||||
azure: ScmIntegrationsGroup<AzureIntegration>;
|
||||
/**
|
||||
* @deprecated in favor of `bitbucketCloud` and `bitbucketServer`
|
||||
*/
|
||||
bitbucket: ScmIntegrationsGroup<BitbucketIntegration>;
|
||||
bitbucketCloud: ScmIntegrationsGroup<BitbucketCloudIntegration>;
|
||||
bitbucketServer: ScmIntegrationsGroup<BitbucketServerIntegration>;
|
||||
gerrit: ScmIntegrationsGroup<GerritIntegration>;
|
||||
@@ -70,7 +65,6 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
awsCodeCommit: AwsCodeCommitIntegration.factory({ config }),
|
||||
azureBlobStorage: AzureBlobStorageIntergation.factory({ config }),
|
||||
azure: AzureIntegration.factory({ config }),
|
||||
bitbucket: BitbucketIntegration.factory({ config }),
|
||||
bitbucketCloud: BitbucketCloudIntegration.factory({ config }),
|
||||
bitbucketServer: BitbucketServerIntegration.factory({ config }),
|
||||
gerrit: GerritIntegration.factory({ config }),
|
||||
@@ -102,13 +96,6 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
return this.byType.azure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated in favor of `bitbucketCloud()` and `bitbucketServer()`
|
||||
*/
|
||||
get bitbucket(): ScmIntegrationsGroup<BitbucketIntegration> {
|
||||
return this.byType.bitbucket;
|
||||
}
|
||||
|
||||
get bitbucketCloud(): ScmIntegrationsGroup<BitbucketCloudIntegration> {
|
||||
return this.byType.bitbucketCloud;
|
||||
}
|
||||
@@ -148,20 +135,10 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
}
|
||||
|
||||
byUrl(url: string | URL): ScmIntegration | undefined {
|
||||
let candidates = Object.values(this.byType)
|
||||
const candidates = Object.values(this.byType)
|
||||
.map(i => i.byUrl(url))
|
||||
.filter(Boolean);
|
||||
|
||||
// Do not return deprecated integrations if there are other options
|
||||
if (candidates.length > 1) {
|
||||
const filteredCandidates = candidates.filter(
|
||||
x => !(x instanceof BitbucketIntegration),
|
||||
);
|
||||
if (filteredCandidates.length !== 0) {
|
||||
candidates = filteredCandidates;
|
||||
}
|
||||
}
|
||||
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('AzureIntegration', () => {
|
||||
azure: [
|
||||
{
|
||||
host: 'h.com',
|
||||
token: 'token',
|
||||
credentials: [{ personalAccessToken: 'token' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -276,50 +276,6 @@ describe('readAzureIntegrationConfig', () => {
|
||||
expect(output).toEqual({ host: 'dev.azure.com' });
|
||||
});
|
||||
|
||||
it('maps deprecated token to credentials', () => {
|
||||
const output = readAzureIntegrationConfig(
|
||||
buildConfig({
|
||||
host: 'dev.azure.com',
|
||||
token: 't',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(output).toEqual({
|
||||
host: 'dev.azure.com',
|
||||
credentials: [
|
||||
{
|
||||
kind: 'PersonalAccessToken',
|
||||
personalAccessToken: 't',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('maps deprecated credential to credentials', () => {
|
||||
const output = readAzureIntegrationConfig(
|
||||
buildConfig({
|
||||
host: 'dev.azure.com',
|
||||
credential: {
|
||||
clientId: 'id',
|
||||
clientSecret: 'secret',
|
||||
tenantId: 'tenantId',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(output).toEqual({
|
||||
host: 'dev.azure.com',
|
||||
credentials: [
|
||||
{
|
||||
kind: 'ClientSecret',
|
||||
clientId: 'id',
|
||||
clientSecret: 'secret',
|
||||
tenantId: 'tenantId',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects config when host is not valid', () => {
|
||||
expect(() =>
|
||||
readAzureIntegrationConfig(buildConfig({ ...valid, host: 7 })),
|
||||
|
||||
@@ -32,24 +32,6 @@ export type AzureIntegrationConfig = {
|
||||
*/
|
||||
host: string;
|
||||
|
||||
/**
|
||||
* The authorization token to use for requests.
|
||||
*
|
||||
* If no token is specified, anonymous access is used.
|
||||
*
|
||||
* @deprecated Use `credentials` instead.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* The credential to use for requests.
|
||||
*
|
||||
* If no credential is specified anonymous access is used.
|
||||
*
|
||||
* @deprecated Use `credentials` instead.
|
||||
*/
|
||||
credential?: AzureDevOpsCredential;
|
||||
|
||||
/**
|
||||
* The credentials to use for requests. If multiple credentials are specified the first one that matches the organization is used.
|
||||
* If not organization matches the first credential without an organization is used.
|
||||
@@ -236,9 +218,17 @@ function asAzureDevOpsCredential(
|
||||
export function readAzureIntegrationConfig(
|
||||
config: Config,
|
||||
): AzureIntegrationConfig {
|
||||
deprecatedConfigCheck(config);
|
||||
|
||||
const host = config.getOptionalString('host') ?? AZURE_HOST;
|
||||
|
||||
let credentialConfigs = config
|
||||
if (!isValidHost(host)) {
|
||||
throw new Error(
|
||||
`Invalid Azure integration config, '${host}' is not a valid host`,
|
||||
);
|
||||
}
|
||||
|
||||
const credentialConfigs = config
|
||||
.getOptionalConfigArray('credentials')
|
||||
?.map(credential => {
|
||||
const result: Partial<AzureDevOpsCredentialLike> = {
|
||||
@@ -257,54 +247,6 @@ export function readAzureIntegrationConfig(
|
||||
return result;
|
||||
});
|
||||
|
||||
const token = config.getOptionalString('token')?.trim();
|
||||
|
||||
if (
|
||||
config.getOptional('credential') !== undefined &&
|
||||
config.getOptional('credentials') !== undefined
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid Azure integration config, 'credential' and 'credentials' cannot be used together. Use 'credentials' instead.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
config.getOptional('token') !== undefined &&
|
||||
config.getOptional('credentials') !== undefined
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid Azure integration config, 'token' and 'credentials' cannot be used together. Use 'credentials' instead.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (token !== undefined) {
|
||||
const mapped = [{ personalAccessToken: token }];
|
||||
credentialConfigs = credentialConfigs?.concat(mapped) ?? mapped;
|
||||
}
|
||||
|
||||
if (config.getOptional('credential') !== undefined) {
|
||||
const mapped = [
|
||||
{
|
||||
organizations: config.getOptionalStringArray(
|
||||
'credential.organizations',
|
||||
),
|
||||
token: config.getOptionalString('credential.token')?.trim(),
|
||||
tenantId: config.getOptionalString('credential.tenantId'),
|
||||
clientId: config.getOptionalString('credential.clientId'),
|
||||
clientSecret: config
|
||||
.getOptionalString('credential.clientSecret')
|
||||
?.trim(),
|
||||
},
|
||||
];
|
||||
credentialConfigs = credentialConfigs?.concat(mapped) ?? mapped;
|
||||
}
|
||||
|
||||
if (!isValidHost(host)) {
|
||||
throw new Error(
|
||||
`Invalid Azure integration config, '${host}' is not a valid host`,
|
||||
);
|
||||
}
|
||||
|
||||
let credentials: AzureDevOpsCredential[] | undefined = undefined;
|
||||
if (credentialConfigs !== undefined) {
|
||||
const errors = credentialConfigs
|
||||
@@ -415,3 +357,19 @@ export function readAzureIntegrationConfigs(
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* These config sections have been removed but to ensure they
|
||||
* don't leak sensitive tokens we have this check in place
|
||||
* to throw an error if found
|
||||
*
|
||||
* @internal
|
||||
* @deprecated To be removed at a later date
|
||||
*/
|
||||
function deprecatedConfigCheck(config: Config) {
|
||||
if (config.getOptional('credential') || config.getOptional('token')) {
|
||||
throw new Error(
|
||||
`Invalid Azure integration config, 'credential' and 'token' have been removed. Use 'credentials' instead.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user