Merge branch 'backstage:master' into chores/stories/nr-2
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-node': minor
|
||||
---
|
||||
|
||||
Added `LocationAnalyzer` type, moved from `@backstage/plugin-catalog-backend`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': minor
|
||||
---
|
||||
|
||||
Deprecated the `LocationAnalyzer` type, which has been moved to `@backstage/plugin-catalog-node`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Added `HarnessURLReader` with `readUrl` support.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-devtools-backend': patch
|
||||
---
|
||||
|
||||
Added discovery property to the readme documentation to ensure that it will build when setting it up as new to a Backstage instance
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': minor
|
||||
---
|
||||
|
||||
The `/alpha` plugin export has had its implementation of the `catalogAnalysisExtensionPoint` updated to reflect the new API.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend-module-aws-alb-provider': patch
|
||||
'@backstage/plugin-auth-backend-module-github-provider': patch
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
fix: Move config declarations to appropriate auth backend modules
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-notifications': patch
|
||||
---
|
||||
|
||||
Increase default and allow modifying notification snackbar auto hide duration
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-github': patch
|
||||
---
|
||||
|
||||
Updated to use the new `catalogAnalysisExtensionPoint` API.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Add `alignGauge` prop to the `GaugeCard`, and a small size version. When `alignGauge` is `'bottom'` the gauge will vertically align the gauge in the cards, even when the card titles span across multiple lines.
|
||||
Add `alignContent` prop to the `InfoCard`, defaulting to `'normal'` with the option of `'bottom'` which vertically aligns the content to the bottom of the card.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Fixed an issue causing the `repo fix` command to set an incorrect `workspace` property using Windows
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': minor
|
||||
---
|
||||
|
||||
Add examples for `gitlab:projectVariable:create` scaffolder action & improve related tests
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Add previously-missing semicolon in file templated by `backstage-cli new --select plugin`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': minor
|
||||
---
|
||||
|
||||
Added `HarnessIntegration` via the `ScmIntegrations` interface.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search': patch
|
||||
---
|
||||
|
||||
Added `aria-label` attribute to DialogTitle element and set `aria-modal` attribute to `true` for improved accessibility in the search modal.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-node': minor
|
||||
---
|
||||
|
||||
Breaking change to `/alpha` API where the `catalogAnalysisExtensionPoint` has been reworked. The `addLocationAnalyzer` method has been renamed to `addScmLocationAnalyzer`, and a new `setLocationAnalyzer` method has been added which allows the full `LocationAnalyzer` implementation to be overridden.
|
||||
@@ -152,6 +152,8 @@ graphviz
|
||||
Hackathons
|
||||
haproxy
|
||||
hardcoded
|
||||
Harness
|
||||
harness
|
||||
Helidon
|
||||
Henneke
|
||||
Heroku
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
diff --git a/dist/changesets-assemble-release-plan.cjs.js b/dist/changesets-assemble-release-plan.cjs.js
|
||||
index ee5c0f67fabadeb112e9f238d8b144a4d125830f..9b0e1a156dd88cee35f82faf718d82a8a8f80325 100644
|
||||
--- a/dist/changesets-assemble-release-plan.cjs.js
|
||||
+++ b/dist/changesets-assemble-release-plan.cjs.js
|
||||
@@ -179,12 +179,23 @@ function getDependencyVersionRanges(dependentPkgJSON, dependencyRelease) {
|
||||
if (!versionRange) continue;
|
||||
|
||||
if (versionRange.startsWith("workspace:")) {
|
||||
+ // intentionally keep other workspace ranges untouched
|
||||
+ // this has to be fixed but this should only be done when adding appropriate tests
|
||||
+ let workspaceRange = versionRange.replace(/^workspace:/, "");
|
||||
+ switch (workspaceRange) {
|
||||
+ case "*":
|
||||
+ // workspace:* actually means the current exact version, and not a wildcard similar to a reguler * range
|
||||
+ workspaceRange = dependencyRelease.oldVersion;
|
||||
+ break;
|
||||
+ case "~":
|
||||
+ case "^":
|
||||
+ // Use ^oldVersion for workspace:^ or ~oldVersion for workspace:~.
|
||||
+ // The version range might have changed in dependent package, but that should have its own changeset bumping that package.
|
||||
+ workspaceRange += dependencyRelease.oldVersion;
|
||||
+ }
|
||||
dependencyVersionRanges.push({
|
||||
depType: type,
|
||||
- versionRange: // intentionally keep other workspace ranges untouched
|
||||
- // this has to be fixed but this should only be done when adding appropriate tests
|
||||
- versionRange === "workspace:*" ? // workspace:* actually means the current exact version, and not a wildcard similar to a reguler * range
|
||||
- dependencyRelease.oldVersion : versionRange.replace(/^workspace:/, "")
|
||||
+ versionRange: workspaceRange,
|
||||
});
|
||||
} else {
|
||||
dependencyVersionRanges.push({
|
||||
-233
@@ -1,233 +0,0 @@
|
||||
# Project Areas
|
||||
|
||||
The Backstage project is divided into several project areas, each covering particular parts of the project. The main driver for each area is ownership of code in the main Backstage repository, as well as other repositories in the Backstage GitHub organization. Each area has a set of maintainers and repository content that they own. There may be no overlap in ownership between areas, which means that any given line in the GitHub code owners file should have only one owner specified. Each area is represented by a team in the Backstage GitHub organization. Apart from certain project-wide concerns, such as the release process, each area is self-governing and chooses their own ways of working. Project areas may also have special interest groups (SIGs) related to their area, but this is not required.
|
||||
|
||||
The project areas as well as their maintainers are listed in the [OWNERS.md](./OWNERS.md) file.
|
||||
|
||||
Each project area must have at least one maintainer. Project area maintainers may have shared ownership with the core maintainers, which in that case is considered an incubating area. The project area maintainers help drive work forward in the area, but they might not yet feel ready to take on ownership. The goal should generally be that these project area maintainers eventually become sole maintainers of the project area. This is to allow for a more smooth onboarding and transition of ownership, where members of the community might for example be new to open source maintainership.
|
||||
|
||||
## Adding new project areas
|
||||
|
||||
Project areas are added by nominating new maintainers for that area. See the sections for becoming a [Project Area Maintainer](#project-area-maintainer).
|
||||
|
||||
Project areas may also by added by splitting existing areas. Every area that is created through this process must have at least one maintainer.
|
||||
|
||||
## Removing project areas
|
||||
|
||||
Project areas are removed by removing all maintainers for that area and removing the corresponding team from the Backstage GitHub organization. The project area can be re-added later if there is a need for it. Reasons for removal may include lack of activity, lack of maintainers, or lack of relevance to the project.
|
||||
|
||||
# Project Roles
|
||||
|
||||
## Contributor
|
||||
|
||||
A Contributor contributes directly to the project and adds value to it. Contributions need not be code. People at the Contributor level may be new contributors, or they may only contribute occasionally.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
- Follow the [CNCF CoC](https://github.com/cncf/foundation/blob/main/code-of-conduct.md)
|
||||
- Follow the project [contributing guide](CONTRIBUTING.md)
|
||||
|
||||
### How to get involved
|
||||
|
||||
- Participate in community discussions
|
||||
- Help other users
|
||||
- Submit bug reports
|
||||
- Comment on issues
|
||||
- Try out new releases
|
||||
- Attend community events
|
||||
|
||||
### How to contribute
|
||||
|
||||
- Report and sometimes resolve issues
|
||||
- Occasionally submit PRs
|
||||
- Contribute to the documentation
|
||||
- Show up at meetings, take notes
|
||||
- Answer questions from other community members
|
||||
- Submit feedback on issues and PRs
|
||||
- Test releases and patches and submit reviews
|
||||
- Run or help run events
|
||||
- Promote the project in public
|
||||
|
||||
## Organization Member
|
||||
|
||||
An org member is a frequent contributor that has become a member of the Backstage GitHub organization. In addition to the responsibilities of contributors, an org member is also expected to be reasonably active in the community through continuous contributions of any type.
|
||||
|
||||
An Organization Member must meet the responsibilities and has the requirements of a Contributor.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
- Continues to contribute regularly, as demonstrated by having at least 10 GitHub contributions per year in [Devstats](https://backstage.devstats.cncf.io/d/48/users-statistics-by-repository-group?orgId=1&var-period=y&var-metric=contributions&var-repogroup_name=All&from=now-1y&to=now&var-users=All), or contributions of a similar effort that might not be captured in Devstats.
|
||||
|
||||
### Requirements
|
||||
|
||||
- Must have at least 10 contributions to the projects in the form of:
|
||||
- Accepted PRs
|
||||
- Helpful PR reviews
|
||||
- Resolving GitHub issues
|
||||
- Or some equivalent contributions to the project
|
||||
- Must have been contributing for at least 3 months
|
||||
- Or is the member of a team that owns a project area, in which case the above requirements do not apply and the member is instead vetted by the project area maintainers
|
||||
|
||||
### Becoming an Organization Member
|
||||
|
||||
Open an issue towards [the community repository](https://github.com/backstage/community) using the [org membership request template](https://github.com/backstage/community/issues/new?template=org_member.yaml&title=Org+Member%3A+%3Cyour-github-login%3E).
|
||||
|
||||
### Privileges
|
||||
|
||||
- Membership in the Backstage GitHub organization
|
||||
|
||||
## Plugin Maintainer
|
||||
|
||||
A Plugin Maintainer is responsible for maintaining an individual Backstage plugin or module. This includes reviewing contributions and responding to issues towards an individual plugin, as well as keeping the plugin up to date.
|
||||
|
||||
Plugin Maintainer is a lightweight form of ownership that is primarily reflected though code owners of the plugin packages in the [CODEOWNERS](./.github/CODEOWNERS) file. Each plugin can have one or more maintainers. If a plugin becomes a significant part of the Backstage ecosystem, it may be promoted to be a distinct project area instead.
|
||||
|
||||
A Plugin Maintainer has all the rights and responsibilities of an Organization Member.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
- Review the majority of PRs towards the plugin
|
||||
- Respond to GitHub issues related to the plugin
|
||||
- Keep the plugin up-to-date with Backstage libraries and other dependencies
|
||||
- Follow the [reviewing guide](REVIEWING.md)
|
||||
|
||||
### Requirements
|
||||
|
||||
- Is an Organization Member
|
||||
- Display knowledge of Backstage's review process and best practices for plugin design
|
||||
- Is supportive of new and occasional contributors and helps get useful PRs in shape to merge
|
||||
|
||||
### Privileges
|
||||
|
||||
- GitHub code owner of the plugin directory, with rights to approve and merge PRs towards the plugin
|
||||
|
||||
### Becoming a Plugin Maintainer
|
||||
|
||||
To become a Plugin Maintainer, you first need to be an Organization Member. You can then file a pull request towards [CODEOWNERS.md](./.github/CODEOWNERS) requesting to be added as a code owner of the plugin directory. Existing code owners of that plugin alongside the core maintainers will then review the request.
|
||||
|
||||
## Project Area Maintainer
|
||||
|
||||
Project Area Maintainers are owners of a particular project area. They are expected to review and merge pull requests towards their area, and also drive development and manage tech health. A Project Area Maintainer also need to commit a certain number of hours per month towards the project, and exercise judgment for the good of the project, independent of their employer. Project Area Maintainers should also mentor new maintainers and participate in and lead community meetings related to their area. New Project Area Maintainers need to be approved by the existing project area maintainers, or the core maintainers if it is a new area.
|
||||
|
||||
The maintainers of a project area may be represented by a team in external organization. In this case, the state as a project area maintainer is tied to the membership in that team. New members of the team may automatically be added as maintainers, as well as removed when they leave. This process is governed autonomously by the team of project area maintainers.
|
||||
|
||||
A Project Area Maintainer has all the rights and responsibilities of an Organization Member.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
- Review PRs towards their project area. Project area maintainers are expected to review at least 20 PRs per year, or the majority of all PRs towards the area, if it is less than 20
|
||||
- Follow the [reviewing guide](REVIEWING.md)
|
||||
- Triage and respond to issues related to their project area
|
||||
- Mentor new project area maintainers
|
||||
- Write refactoring PRs
|
||||
- Determine strategy and policy for the project area
|
||||
- Participate in or leading community meetings related to their project area
|
||||
|
||||
### Requirements
|
||||
|
||||
- Is an Organization Member
|
||||
- Have made at least 5 meaningful contributions towards the project area
|
||||
- Demonstrates knowledge of their project area, and how it fits into the larger Backstage project
|
||||
- Is able to exercise judgment for the good of the project, independent of their employer, friends, or team
|
||||
- Mentors other contributors and project area maintainers
|
||||
- Can commit to spending at least 16 hours per month working on the project, preferably distributed evenly across the month
|
||||
|
||||
### Privileges
|
||||
|
||||
- Approve and merge PRs towards their project area
|
||||
- Drive the direction and roadmap of their project area
|
||||
|
||||
### Becoming a Project Area Maintainer
|
||||
|
||||
If you are interested in becoming a project area maintainer, reach out to the existing maintainers for that area. If you wish to become a maintainer for a new area, reach out to the core maintainers.
|
||||
|
||||
Any current project area maintainer or core maintainer may nominate a new project area maintainer by opening a PR towards the [OWNERS.md](OWNERS.md) file. A majority of the project area maintainers for that area must approve the PR. If there are no existing maintainers for that area, the PR must be approved by a majority of the core maintainers.
|
||||
|
||||
## Core Maintainer
|
||||
|
||||
Core Maintainers are responsible for the Backstage project as a whole. They help review and merge project-level pull requests as well as coordinate work affecting multiple project areas. A core maintainer needs to commit the majority of their working time towards the project, and exercise judgment for the good of the project, independent of their employer. Core maintainers should also mentor and seek out new maintainers, lead community meetings, and communicate with the CNCF on behalf of the project. To become a core maintainer one needs to have been the maintainer of a number of different project areas, demonstrate a deep knowledge of large parts of the Backstage project, and be backed by the existing core maintainers.
|
||||
|
||||
A Core Maintainer have all the rights and responsibilities of a Project Area Maintainer.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
- Take part in the incoming issue and PR triage and review process. PRs are shared equally among all maintainers
|
||||
- Mentor new Project Area Maintainers and Plugin Maintainers
|
||||
- Drive refactoring and manage tech health across the entire project
|
||||
- Participate in CNCF maintainer activities
|
||||
- Respond to security incidents in accordance to our [security policy](./SECURITY.md)
|
||||
- Determine strategy and policy for the project
|
||||
- Participate in or leading community meetings
|
||||
|
||||
### Requirements
|
||||
|
||||
- Experience as a Project Area Maintainer for at least 6 months
|
||||
- Demonstrates a broad knowledge of the project across multiple areas
|
||||
- Is able to exercise judgment for the good of the project, independent of their employer, friends, or team
|
||||
- Mentors other contributors
|
||||
- Can commit to spending at least 10 days per month working on the project
|
||||
|
||||
### Privileges
|
||||
|
||||
- Approve PRs that fall outside any specific project area
|
||||
- Merge PRs to any area of the project
|
||||
- Represent the project in public as a Maintainer
|
||||
- Communicate with the CNCF on behalf of the project
|
||||
- Have a vote in Maintainer decision-making meetings
|
||||
|
||||
### Becoming a Core Maintainer
|
||||
|
||||
Any core maintainer or end user sponsor may nominate a new core maintainer by opening a PR towards the [OWNERS.md](OWNERS.md) file. Core maintainers must be approved by a majority of the existing core maintainers and end user sponsors.
|
||||
|
||||
## End User Sponsors
|
||||
|
||||
### Role of a Backstage End User Sponsor
|
||||
|
||||
- Provide support for Backstage by removing blockers, securing funding, providing advocacy, feedback, and ensuring project continuity and long term success
|
||||
- Assist Backstage maintainers in prioritizing upcoming roadmap items and planned work
|
||||
- Provide neutral mediation for any disputes that arise as part of the project
|
||||
|
||||
### Backstage End User Sponsor Membership
|
||||
|
||||
The End User Sponsors group comprises at most 5 people. To be eligible for membership in the group, you or the company where you work you must:
|
||||
|
||||
- Be responsible for and end user of a production Backstage deployment of non-trivial size
|
||||
- Be active contributors to the open source project
|
||||
- Be willing and able to attend regularly-scheduled End User Sponsor meetings
|
||||
- Abide by [CNCF CoC](https://github.com/cncf/foundation/blob/main/code-of-conduct.md)
|
||||
|
||||
Candidates for membership will be nominated by current Sponsor members or by Backstage maintainers. If there are more nominations than Sponsor seats remaining, existing sponsors shall vote on the candidates, and the candidates with the most votes will become Sponsors. Any ties will be broken by current Backstage sponsors.
|
||||
|
||||
# Conflict resolution and voting
|
||||
|
||||
In general, we prefer that technical issues and membership are amicably worked out between the persons involved. If a dispute cannot be decided independently, the sponsors and core maintainers can be called in to decide an issue. If the sponsors and maintainers themselves cannot decide an issue, the issue will be resolved by voting.
|
||||
|
||||
In all cases in this document where voting is mentioned, the voting process is a simple majority in which each sponsor receives two votes and each core maintainer receives one vote. If such a majority is reached, the vote is said to have _passed_.
|
||||
|
||||
## Inactivity
|
||||
|
||||
It is important for contributors to be and stay active to set an example and show commitment to the project. Inactivity is harmful to the project as it may lead to unexpected delays, contributor attrition, and a loss of trust in the project.
|
||||
|
||||
Inactivity is measured by periods of no contributions without explanation, for longer than:
|
||||
|
||||
- Core Maintainer: 2 months
|
||||
- Project Area Maintainer: 4 months
|
||||
- Plugin Maintainer: 6 months
|
||||
- Organization Member: 12 months
|
||||
|
||||
Consequences of being inactive include:
|
||||
|
||||
- Involuntary removal or demotion
|
||||
- Being asked to move to Emeritus status
|
||||
|
||||
## Involuntary Removal or Demotion
|
||||
|
||||
Involuntary removal/demotion of a contributor happens when responsibilities and requirements aren't being met. This may include repeated patterns of inactivity, extended period of inactivity, a period of failing to meet the requirements of your role, and/or a violation of the Code of Conduct. This process is important because it protects the community and its deliverables while also opens up opportunities for new contributors to step in.
|
||||
|
||||
Involuntary removal or demotion is handled through a vote by a majority of the current Core Maintainers. Some aspects of this process may be automated, such as removal after periods of inactivity.
|
||||
|
||||
## Stepping Down/Emeritus Process
|
||||
|
||||
If and when contributors' commitment levels change, contributors can consider stepping down (moving down the contributor ladder) vs moving to emeritus status (completely stepping away from the project).
|
||||
|
||||
Contact the Maintainers about changing to Emeritus status, or reducing your contributor level.
|
||||
@@ -1,5 +1,5 @@
|
||||
- See [CONTRIBUTING.md](CONTRIBUTING.md) for general contribution guidelines.
|
||||
- See [GOVERNANCE.md](GOVERNANCE.md) for governance guidelines and responsibilities.
|
||||
- See [GOVERNANCE.md](https://github.com/backstage/community/blob/main/GOVERNANCE.md) for governance guidelines and responsibilities.
|
||||
|
||||
## Core Maintainers
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 583 KiB |
+1
-1
@@ -37,7 +37,7 @@ more, read our blog post,
|
||||
|
||||
Yes, we've already started releasing open source versions of some of the plugins
|
||||
we use here, and we'll continue to do so.
|
||||
[Plugins](#what-is-a-plugin-in-backstage) are the building blocks of
|
||||
[Plugins](technical.md#what-is-a-plugin-in-backstage) are the building blocks of
|
||||
functionality in Backstage. We have over 120 plugins inside Spotify — many of
|
||||
those are specialized for our use, so will remain internal and proprietary to
|
||||
us. But we estimate that about a third of our existing plugins make good open
|
||||
|
||||
@@ -154,7 +154,7 @@ maintains Backstage in your own environment.
|
||||
|
||||
For more information, see our
|
||||
[Owners](https://github.com/backstage/backstage/blob/master/OWNERS.md) and
|
||||
[Governance](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md).
|
||||
[Governance](https://github.com/backstage/community/blob/main/GOVERNANCE.md).
|
||||
|
||||
### Does Spotify provide a managed version of Backstage?
|
||||
|
||||
|
||||
@@ -738,3 +738,39 @@ The `projectSlug` filter generates a project slug from a repository URL
|
||||
|
||||
- **Input**: `github.com?repo=backstage&org=backstage`
|
||||
- **Output**: `backstage/backstage`
|
||||
|
||||
## Template Editor
|
||||
|
||||
Writing template is most of the times an iterative process. You will need to test your template to make sure it has a good user experience and that it works as expected. To help on this process the scaffolder comes with a build in template editor that allows you to test your template in a real environment for querying data and execute the actions on dry-run mode to see the results of those one.
|
||||
|
||||
To access to the template editor you can go to the templates page and select "Template Editor" from the context menu or navigate to the `{scaffolder-path}/edit` url. (i.e. the default route would be `/create/edit`)
|
||||
|
||||

|
||||
|
||||
The template editor has 3 main sections:
|
||||
|
||||
1. **Load Template Directory**: Load a local template directory, allowing you to both edit and try executing your own template.
|
||||
2. **Edit Template Form**: Preview and edit a template form, either using a sample template or by loading a template from the catalog.
|
||||
3. **Custom Field Explorer**: View and play around with available installed custom field extensions.
|
||||
|
||||
### Load Template Directory
|
||||
|
||||
Allow to load a directory on your local file system that contains a template and editing the files in it while previewing the form and executing the template.
|
||||
|
||||

|
||||
|
||||
If you complete the form in the right side and click on `Create` button, the template will be executed in dry-run mode and the result will be shown in the `Dry-run result` drawer that will pop-up at the bottom of the screen.
|
||||
|
||||
Here we could find all the file system results of the template execution as well as the logs of each action that was executed.
|
||||
|
||||

|
||||
|
||||
### Edit Template Form
|
||||
|
||||
This is a reduced version of the template editor that allows you to select any template from the catalog and do some modifications on the form presented to the user to test some changes.
|
||||
|
||||
Have in mind that changes in this form will not be saved on the template and is meant to test out changes to replicate them manually on the template file after.
|
||||
|
||||
### Custom Field Explorer
|
||||
|
||||
The custom filed explorer allows you to select any custom field loaded on the backstage instance and test different values and configurations.
|
||||
|
||||
@@ -6,4 +6,50 @@ sidebar_label: Configuring Extensions
|
||||
description: Documentation for how to configure extensions in a Backstage app
|
||||
---
|
||||
|
||||
TODO
|
||||
All extensions in a Backstage app can be configured through static configuration. This configuration is all done under the `app.extensions` configuration key. For more general information on how to write configuration for Backstage, see the section on [writing configuration](../../conf/writing.md).
|
||||
|
||||
## Extension Configuration Schema
|
||||
|
||||
This section focuses on the format of the `app.extensions` configuration and the various shorthands that are available.
|
||||
|
||||
The most complete and verbose format for configuring an individual extensions is as follows:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
extensions:
|
||||
- <id>:
|
||||
attachTo:
|
||||
id: <parent-id>
|
||||
input: <input-name>
|
||||
disabled: <true/false>
|
||||
config: <extension-specific-config->
|
||||
```
|
||||
|
||||
All of the top-level fields are optional: `attachTo`, `disabled`, and `config`. Every extension implementation must provide defaults for all of these fields that will be used if they are not provided in the configuration.
|
||||
|
||||
Note that `app.extensions` is always an array rather than an object. For example, the following is invalid:
|
||||
|
||||
```yaml title="INVALID"
|
||||
app:
|
||||
extensions:
|
||||
<id>: # Invalid, this should be an array item, `app.extensions` is now an object
|
||||
config: ...
|
||||
```
|
||||
|
||||
In addition to this schema, there are a number of shorthands available:
|
||||
|
||||
Rather than a full object, you can specify just the ID of the extension as a string. This is equivalent to setting `disabled` to `false`:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
extensions:
|
||||
- ‘<id>’
|
||||
```
|
||||
|
||||
You can enable/disable individual extension by ID, in this case the value is a boolean:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
extensions:
|
||||
- <id>: <true/false>
|
||||
```
|
||||
|
||||
@@ -117,7 +117,7 @@ You can then also add any additional extensions that you may need to create as p
|
||||
|
||||
[Utility API](../utility-apis/01-index.md) factories are now installed as extensions instead. Pass the existing factory to `createApiExtension` and install it in the app. For more information, see the section on [configuring Utility APIs](../utility-apis/04-configuring.md).
|
||||
|
||||
For example, the following API configuration:
|
||||
For example, the following `apis` configuration:
|
||||
|
||||
```ts
|
||||
const app = createApp({
|
||||
@@ -151,15 +151,75 @@ Icons are currently installed through the usual options to `createApp`, but will
|
||||
|
||||
Plugins are now passed through the `features` options instead.
|
||||
|
||||
For example, the following `plugins` configuration:
|
||||
|
||||
```tsx
|
||||
import { homePlugin } from '@backstage/plugin-home';
|
||||
|
||||
createApp({
|
||||
// ...
|
||||
plugins: [homePlugin],
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Can be converted to the following `features` configuration:
|
||||
|
||||
```tsx
|
||||
// plugins are now default exported via alpha subpath
|
||||
import homePlugin from '@backstage/plugin-home/alpha';
|
||||
|
||||
createApp({
|
||||
// ...
|
||||
features: [homePlugin],
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Plugins don't even have to be imported manually after installing their package if [features discovery](../architecture/02-app.md#feature-discovery) is enabled.
|
||||
|
||||
```yaml title="in app-config.yaml"
|
||||
app:
|
||||
# Enabling plugin and override features discovery
|
||||
experimental: 'all'
|
||||
```
|
||||
|
||||
### `featureFlags`
|
||||
|
||||
Declaring features flags in the app is no longer supported, move these declarations to the appropriate plugins instead.
|
||||
|
||||
For example, the following app feature flags configuration:
|
||||
|
||||
```tsx
|
||||
createApp({
|
||||
// ...
|
||||
featureFlags: [
|
||||
{
|
||||
pluginId: '',
|
||||
name: 'tech-radar',
|
||||
description: 'Enables the tech radar plugin',
|
||||
},
|
||||
],
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Can be converted to the following plugin configuration:
|
||||
|
||||
```tsx
|
||||
createPlugin({
|
||||
id: 'tech-radar',
|
||||
// ...
|
||||
featureFlags: [{ name: 'tech-radar' }],
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
### `components`
|
||||
|
||||
Many app components are now installed as extensions instead using `createComponentExtension`. See the section on [configuring app components](./01-index.md#configure-your-app) for more information.
|
||||
|
||||
The `Router` component is now a built-in extension that you can override using `createRouterExtension`.
|
||||
The `Router` component is now a built-in extension that you can [override](../architecture/05-extension-overrides.md) using `createRouterExtension`.
|
||||
|
||||
The Sign-in page is now installed as an extension using the `createSignInPageExtension` instead.
|
||||
|
||||
@@ -277,6 +337,35 @@ const app = createApp({
|
||||
|
||||
Translations are now installed as extensions, using `createTranslationExtension`.
|
||||
|
||||
For example, the following translations configuration:
|
||||
|
||||
```tsx
|
||||
import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';
|
||||
createApp({
|
||||
// ...
|
||||
__experimentalTranslations: {
|
||||
resources: [
|
||||
createTranslationMessages({
|
||||
ref: catalogTranslationRef,
|
||||
catalog_page_create_button_title: 'Create Software',
|
||||
}),
|
||||
],
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Can be converted to the following extension:
|
||||
|
||||
```tsx
|
||||
createTranslationExtension({
|
||||
resource: createTranslationMessages({
|
||||
ref: catalogTranslationRef,
|
||||
catalog_page_create_button_title: 'Create Software',
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
## Gradual Migration
|
||||
|
||||
After updating all `createApp` options as well as using `convertLegacyApp` to use your existing app structure, you should be able to start up the app and see that it still works. If that is not the case, make sure you read any error messages that you may see in the app as they can provide hints on what you need to fix. If you are still stuck, you can check if anyone else ran into the same issue in our [GitHub issues](https://github.com/backstage/backstage/issues), or ask for help in our [community Discord](https://discord.gg/backstage-687207715902193673).
|
||||
@@ -368,7 +457,7 @@ The entity pages are typically defined in `packages/app/src/components/catalog`
|
||||
|
||||
New apps feature a built-in sidebar extension (`app/nav`) that will render all nav item extensions provided by plugins. This is a placeholder implementation and not intended as a long-term solution. In the future we will aim to provide a more flexible sidebar extension that allows for more customization out of the box.
|
||||
|
||||
Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an extension override:
|
||||
Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an [extension override](../architecture/05-extension-overrides.md):
|
||||
|
||||
```tsx
|
||||
const nav = createExtension({
|
||||
@@ -435,3 +524,24 @@ export default app.createRoot(
|
||||
```
|
||||
|
||||
Any app root wrapper needs to be migrated to be an extension, using `createAppRootWrapperExtension`. Note that if you have multiple wrappers they must be completely independent of each other, i.e. the order in which they the appear in the React tree should not matter. If that is not the case then you should group them into a single wrapper.
|
||||
|
||||
Here is an example converting the `CustomAppBarrier` into extension:
|
||||
|
||||
```tsx
|
||||
createApp({
|
||||
// ...
|
||||
features: [
|
||||
createExtensionOverrides({
|
||||
extensions: [
|
||||
createAppRootWrapperExtension({
|
||||
name: 'CustomAppBarrier',
|
||||
// Whenever your component uses legacy core packages, wrap it with "compatWrapper"
|
||||
// e.g. props => compatWrapper(<CustomAppBarrier {...props} />)
|
||||
Component: CustomAppBarrier,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
id: locations
|
||||
title: Harness Locations
|
||||
sidebar_label: Locations
|
||||
description: Integrating source code stored in Harness Code into the Backstage catalog
|
||||
---
|
||||
|
||||
The Harness Code integration supports loading catalog entities from a hosted repository. Entities can be added to
|
||||
[static catalog configuration](../../features/software-catalog/configuration.md),
|
||||
registered with the
|
||||
[catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import)
|
||||
plugin.
|
||||
|
||||
## Configuration
|
||||
|
||||
To use this integration, add configuration to your root `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
integrations:
|
||||
harness:
|
||||
- host: app.harness.io
|
||||
token: ${HARNESS_CODE_BEARER_TOKEN}
|
||||
apiKey: ${HARNESS_CODE_APIKEY}
|
||||
```
|
||||
|
||||
Directly under the `harness` key is a list of provider configurations, where you
|
||||
can list the Harness instances you want to be able to fetch
|
||||
|
||||
check out https://developer.harness.io/docs/platform/automation/api/add-and-manage-api-keys/ for more information
|
||||
|
||||
- `host`: The host of the Harness Code instance that you want to match on.
|
||||
- `token` (optional): The password or api token to authenticate with.
|
||||
- `apiKey` (optional): The apiKey to authenticate with.
|
||||
@@ -15,6 +15,32 @@ See how to install Datadog Events in your app
|
||||
|
||||
## Logging
|
||||
|
||||
### New Backend
|
||||
|
||||
The backend supplies a central logging service, [`rootLogger`](../backend-system/core-services/root-logger.md), as well as a plugin based logger, [`logger`](../backend-system/core-services/logger.md) from `coreServices`. To add additional granularity to your logs, you can create children from the plugin based logger, using the `.child()` method and provide is with JSON data. For example, if you wanted to log items for a specific span in your plugin, you could do
|
||||
|
||||
```ts
|
||||
export function createRouter({ logger }) {
|
||||
const router = Router();
|
||||
|
||||
router.post('/task/:taskId/queue', (req, res) => {
|
||||
const { taskId } = req.params;
|
||||
const taskLogger = logger.child({ task: taskId });
|
||||
taskLogger.log('Queueing this task.');
|
||||
});
|
||||
|
||||
router.get('/task/:taskId/results', (req, res) => {
|
||||
const { taskId } = req.params;
|
||||
const taskLogger = logger.child({ task: taskId });
|
||||
taskLogger.log('Getting the results of this task.');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
You can also add additional metadata to all logs for your Backstage instance by overriding the `rootLogger` implementation, you can see an example in [the `logger` docs](../backend-system/core-services/logger.md#configuring-the-service).
|
||||
|
||||
### Old Backend
|
||||
|
||||
The backend supplies a central [winston](https://github.com/winstonjs/winston)
|
||||
root logger that plugins are expected to use for their logging needs. In the
|
||||
default production setup, it emits structured JSON logs on stdout, with a field
|
||||
@@ -37,7 +63,39 @@ An example log line could look as follows:
|
||||
|
||||
## Health Checks
|
||||
|
||||
The example backend in the Backstage repository
|
||||
### New Backend
|
||||
|
||||
The new backend is moving towards health checks being plugin-based, as such there is no current plugin for providing a health check route. You can add this yourself easily though,
|
||||
|
||||
```ts
|
||||
import {
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
const healthCheck = createBackendPlugin({
|
||||
pluginId: 'healthcheck',
|
||||
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
rootHttpRouter: coreServices.rootHttpRouter,
|
||||
},
|
||||
init: async ({ rootHttpRouter }) => {
|
||||
// You can adjust the route name and response as you need.
|
||||
rootHttpRouter.use('/healthcheck', (req, res) => {
|
||||
res.json({ status: 'ok' });
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Old Backend
|
||||
|
||||
The example old backend in the Backstage repository
|
||||
[supplies](https://github.com/backstage/backstage/blob/bc18571b7a742863a770b2a54e785d6bbef7e184/packages/backend/src/index.ts#L99)
|
||||
a very basic health check endpoint on the `/healthcheck` route. You may add such
|
||||
a handler to your backend as well, and supply your own logic to it that fits
|
||||
|
||||
@@ -15,6 +15,16 @@ can be the best choice for communicating with an API.
|
||||
|
||||
The plugin is already added to a default Backstage project.
|
||||
|
||||
### New Backend
|
||||
|
||||
To add it to a project, add the following line in `packages/backend/src/index.ts`:
|
||||
|
||||
```ts
|
||||
backend.add(import('@backstage/plugin-proxy-backend/alpha'));
|
||||
```
|
||||
|
||||
### Old Backend
|
||||
|
||||
In `packages/backend/src/index.ts`:
|
||||
|
||||
```ts
|
||||
|
||||
@@ -66,9 +66,9 @@ Speaking of reviewers and maintainers…
|
||||
|
||||
## Adding reviewers and maintainers
|
||||
|
||||
[](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#reviewers)
|
||||
[](https://github.com/backstage/community/blob/main/GOVERNANCE.md#reviewers)
|
||||
|
||||
We have introduced [reviewers](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#reviewers) to the project! By adding this new role, we’ve expanded the number of people who are permitted to approve and merge pull requests. This will offload some of the review work from the maintainers, simplifying and speeding up the review process for contributors.
|
||||
We have introduced [reviewers](https://github.com/backstage/community/blob/main/GOVERNANCE.md#reviewers) to the project! By adding this new role, we’ve expanded the number of people who are permitted to approve and merge pull requests. This will offload some of the review work from the maintainers, simplifying and speeding up the review process for contributors.
|
||||
|
||||
Of course, with these new efforts, we expect even more companies to adopt Backstage, which means the platform will continue to grow, and the number of PRs will continue to grow with it. As that happens, we hope to add to both the maintainer and reviewer teams in the future.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ On Tuesday the Backstage maintainers hosted a jam-packed project meeting. The co
|
||||
|
||||

|
||||
|
||||
Core maintainers [Ben Lambert](https://github.com/benjdlambert) and [Patrik Oldsberg](https://github.com/Rugvip) took center stage on Wednesday for the Backstage Maintainer Track: State of Backstage in 2023 talk. Backstage has officially hit over 1,000 adopters and 1,000 contributors – so it’s apt timing to modernize the governance model for the project. Taking pointers from the [CNCF Contributor Ladder Governance Template](https://contribute.cncf.io/maintainers/templates/), a new [Backstage Governance Model](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md) is now in effect! Patrik walked us through the new ladder model which introduces a number of changes, one being the addition of [project area maintainers](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#project-area-maintainer). This role lets members of the community take increased ownership over a specific area of interest, like Catalog, Discoverability, TechDocs, Helm Charts, and Kubernetes. New project areas that will be added include Permissions and Software Templates. New project areas can be proposed by nominating a project area maintainer for the area. The new model also adds an organization member role for contributors who want to take a more active role in the Backstage community. You can open an issue to become an organization member [here](https://github.com/backstage/community/issues/new/choose).
|
||||
Core maintainers [Ben Lambert](https://github.com/benjdlambert) and [Patrik Oldsberg](https://github.com/Rugvip) took center stage on Wednesday for the Backstage Maintainer Track: State of Backstage in 2023 talk. Backstage has officially hit over 1,000 adopters and 1,000 contributors – so it’s apt timing to modernize the governance model for the project. Taking pointers from the [CNCF Contributor Ladder Governance Template](https://contribute.cncf.io/maintainers/templates/), a new [Backstage Governance Model](https://github.com/backstage/community/blob/main/GOVERNANCE.md) is now in effect! Patrik walked us through the new ladder model which introduces a number of changes, one being the addition of [project area maintainers](https://github.com/backstage/community/blob/main/GOVERNANCE.md#project-area-maintainer). This role lets members of the community take increased ownership over a specific area of interest, like Catalog, Discoverability, TechDocs, Helm Charts, and Kubernetes. New project areas that will be added include Permissions and Software Templates. New project areas can be proposed by nominating a project area maintainer for the area. The new model also adds an organization member role for contributors who want to take a more active role in the Backstage community. You can open an issue to become an organization member [here](https://github.com/backstage/community/issues/new/choose).
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ For those who depended on these plugins, migrating is as simple as `yarn backsta
|
||||
|
||||
## The community plugins repo
|
||||
|
||||
Some of you who have been around a while, or have seen our [Maintainer Track talks](https://www.youtube.com/watch?v=ONMBYnhxnNU) at KubeCon, might have seen [this RFC](https://github.com/backstage/backstage/issues/20266) which outlines some issues with the scale of the `backstage/backstage` monorepo, and us as maintainers being the de facto owners of all plugins without a [project area](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#project-area) or [plugin maintainer](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#project-area-maintainer).
|
||||
Some of you who have been around a while, or have seen our [Maintainer Track talks](https://www.youtube.com/watch?v=ONMBYnhxnNU) at KubeCon, might have seen [this RFC](https://github.com/backstage/backstage/issues/20266) which outlines some issues with the scale of the `backstage/backstage` monorepo, and us as maintainers being the de facto owners of all plugins without a [project area](https://github.com/backstage/community/blob/main/GOVERNANCE.md#project-area) or [plugin maintainer](https://github.com/backstage/community/blob/main/GOVERNANCE.md#project-area-maintainer).
|
||||
|
||||
There was some great discussion in this issue, and some great ideas. One of the ideas was to create a dedicated home for community plugins, with all the burden of release tooling and workspace tooling already set up, which is a pretty big barrier for people wanting to create plugins for Backstage in their own organization or personal account. These plugins would then have the ability to release independently of the main monorepo, and have their own release cadence, which is something that we've been looking at exploring for a while.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ author: Marcin Kozlowski
|
||||
authorUrl: https://betterscan.io
|
||||
category: Security
|
||||
description: View security scanned vulnerabilities in Code and Cloud scanned using Open Source and proprietary scanners directly in Backstage.
|
||||
documentation: https://github.com/marcinguy/betterscan-ce
|
||||
documentation: https://www.npmjs.com/package/@marcinguy/backstage-plugin-betterscan
|
||||
iconUrl: https://uploads-ssl.webflow.com/6339e3b81867539b5fe2498d/633a1643dcb06d3029867161_g4.svg
|
||||
npmPackageName: '@marcinguy/backstage-plugin-betterscan'
|
||||
addedDate: '2022-12-08'
|
||||
|
||||
@@ -238,6 +238,11 @@
|
||||
"label": "Gitea",
|
||||
"items": ["integrations/gitea/locations"]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Harness",
|
||||
"items": ["integrations/harness/locations"]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Google GCS",
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
},
|
||||
"prettier": "@spotify/prettier-config",
|
||||
"resolutions": {
|
||||
"@changesets/assemble-release-plan@^6.0.0": "patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch",
|
||||
"@material-ui/pickers@^3.2.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch",
|
||||
"@material-ui/pickers@^3.3.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch",
|
||||
"@types/react": "^18",
|
||||
|
||||
@@ -22,6 +22,17 @@ function msg(info: TransformableInfo): TransformableInfo {
|
||||
}
|
||||
|
||||
describe('WinstonLogger', () => {
|
||||
it('creates a winston logger instance with default options', () => {
|
||||
const logger = WinstonLogger.create({});
|
||||
expect(logger).toBeInstanceOf(WinstonLogger);
|
||||
});
|
||||
|
||||
it('creates a child logger', () => {
|
||||
const logger = WinstonLogger.create({});
|
||||
const childLogger = logger.child({ plugin: 'test-plugin' });
|
||||
expect(childLogger).toBeInstanceOf(WinstonLogger);
|
||||
});
|
||||
|
||||
it('redacter should redact and escape regex', () => {
|
||||
const redacter = WinstonLogger.redacter();
|
||||
const log = {
|
||||
@@ -47,4 +58,24 @@ describe('WinstonLogger', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('redacter should redact nested object', () => {
|
||||
const redacter = WinstonLogger.redacter();
|
||||
const log = {
|
||||
level: 'error',
|
||||
message: {
|
||||
nested: 'hello (world) from nested object',
|
||||
},
|
||||
};
|
||||
|
||||
redacter.add(['hello']);
|
||||
expect(redacter.format.transform(msg(log))).toEqual(
|
||||
msg({
|
||||
...log,
|
||||
message: {
|
||||
nested: '[REDACTED] (world) from nested object',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ import { GiteaIntegration } from '@backstage/integration';
|
||||
import { GithubCredentialsProvider } from '@backstage/integration';
|
||||
import { GithubIntegration } from '@backstage/integration';
|
||||
import { GitLabIntegration } from '@backstage/integration';
|
||||
import { HarnessIntegration } from '@backstage/integration';
|
||||
import { HostDiscovery as HostDiscovery_2 } from '@backstage/backend-app-api';
|
||||
import { HttpAuthService } from '@backstage/backend-plugin-api';
|
||||
import { IdentityService } from '@backstage/backend-plugin-api';
|
||||
@@ -512,6 +513,23 @@ export class GitlabUrlReader implements UrlReader {
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class HarnessUrlReader implements UrlReader {
|
||||
constructor(integration: HarnessIntegration);
|
||||
// (undocumented)
|
||||
static factory: ReaderFactory;
|
||||
// (undocumented)
|
||||
read(url: string): Promise<Buffer>;
|
||||
// (undocumented)
|
||||
readTree(): Promise<ReadTreeResponse>;
|
||||
// (undocumented)
|
||||
readUrl(url: string, options?: ReadUrlOptions): Promise<ReadUrlResponse>;
|
||||
// (undocumented)
|
||||
search(): Promise<SearchResponse>;
|
||||
// (undocumented)
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const HostDiscovery: typeof HostDiscovery_2;
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2024 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 { setupRequestMockHandlers } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { HarnessIntegration, readHarnessConfig } from '@backstage/integration';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { getVoidLogger } from '../logging';
|
||||
import { UrlReaderPredicateTuple } from './types';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
import getRawBody from 'raw-body';
|
||||
import { HarnessUrlReader } from './HarnessUrlReader';
|
||||
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
jest.mock('../scm', () => ({
|
||||
Git: {
|
||||
fromAuth: () => ({
|
||||
clone: jest.fn(() => Promise.resolve({})),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
const harnessProcessor = new HarnessUrlReader(
|
||||
new HarnessIntegration(
|
||||
readHarnessConfig(
|
||||
new ConfigReader({
|
||||
host: 'app.harness.io',
|
||||
token: 'p',
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => {
|
||||
return HarnessUrlReader.factory({
|
||||
config: new ConfigReader(config),
|
||||
logger: getVoidLogger(),
|
||||
treeResponseFactory,
|
||||
});
|
||||
};
|
||||
const responseBuffer = Buffer.from('Apache License');
|
||||
const harnessApiResponse = (content: any) => {
|
||||
return content;
|
||||
};
|
||||
|
||||
const handlers = [
|
||||
rest.get(
|
||||
'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/all-apis.yaml',
|
||||
(_req, res, ctx) => {
|
||||
return res(ctx.status(500), ctx.json({ message: 'Error!!!' }));
|
||||
},
|
||||
),
|
||||
rest.get(
|
||||
'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/404error.yaml',
|
||||
(_req, res, ctx) => {
|
||||
return res(ctx.status(404), ctx.json({ message: 'File not found.' }));
|
||||
},
|
||||
),
|
||||
rest.get(
|
||||
'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/stream.TXT',
|
||||
(_req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.body(harnessApiResponse(responseBuffer.toString())),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
rest.get(
|
||||
'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/buffer.TXT',
|
||||
(_req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.body(harnessApiResponse(responseBuffer.toString())),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
describe('HarnessUrlReader', () => {
|
||||
const worker = setupServer(...handlers);
|
||||
setupRequestMockHandlers(worker);
|
||||
beforeAll(() => worker.listen({ onUnhandledRequest: 'bypass' }));
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('reader factory', () => {
|
||||
it('creates a reader.', () => {
|
||||
const readers = createReader({
|
||||
integrations: {
|
||||
harness: [{ host: 'app.harness.io' }],
|
||||
},
|
||||
});
|
||||
expect(readers).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should not create a default entry.', () => {
|
||||
const readers = createReader({
|
||||
integrations: {},
|
||||
});
|
||||
expect(readers).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('predicates', () => {
|
||||
it('returns true for the configured host', () => {
|
||||
const readers = createReader({
|
||||
integrations: {
|
||||
harness: [{ host: 'app.harness.io' }],
|
||||
},
|
||||
});
|
||||
const predicate = readers[0].predicate;
|
||||
|
||||
expect(predicate(new URL('https://app.harness.io/path'))).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for a different host.', () => {
|
||||
const readers = createReader({
|
||||
integrations: {
|
||||
harness: [{ host: 'app.harness.io' }],
|
||||
},
|
||||
});
|
||||
const predicate = readers[0].predicate;
|
||||
|
||||
expect(predicate(new URL('https://github.com/path'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readUrl part 1', () => {
|
||||
it('should be able to read file contents as buffer', async () => {
|
||||
const result = await harnessProcessor.readUrl(
|
||||
'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/buffer.TXT',
|
||||
);
|
||||
const buffer = await result.buffer();
|
||||
expect(buffer.toString()).toBe(responseBuffer.toString());
|
||||
});
|
||||
|
||||
it('should be able to read file contents as stream', async () => {
|
||||
const result = await harnessProcessor.readUrl(
|
||||
'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/stream.TXT',
|
||||
);
|
||||
const fromStream = await getRawBody(result.stream!());
|
||||
expect(fromStream.toString()).toBe(responseBuffer.toString());
|
||||
});
|
||||
|
||||
it('should raise NotFoundError on 404.', async () => {
|
||||
await expect(
|
||||
harnessProcessor.readUrl(
|
||||
'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml',
|
||||
),
|
||||
).rejects.toThrow(
|
||||
'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/raw/404error.yaml?routingId=accountId&git_ref=refMain, 404 Not Found',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error on non 404 errors.', async () => {
|
||||
await expect(
|
||||
harnessProcessor.readUrl(
|
||||
'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml',
|
||||
),
|
||||
).rejects.toThrow(
|
||||
'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/raw/all-apis.yaml?routingId=accountId&git_ref=refMain, 500 Internal Server Error',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2025 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 {
|
||||
getHarnessRequestOptions,
|
||||
getHarnessFileContentsUrl,
|
||||
HarnessIntegration,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import { ReadUrlOptions, ReadUrlResponse } from './types';
|
||||
import {
|
||||
ReaderFactory,
|
||||
ReadTreeResponse,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
import {
|
||||
AuthenticationError,
|
||||
NotFoundError,
|
||||
NotModifiedError,
|
||||
} from '@backstage/errors';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
/**
|
||||
* Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for the Harness code v1 api.
|
||||
*
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class HarnessUrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config }) => {
|
||||
return ScmIntegrations.fromConfig(config)
|
||||
.harness.list()
|
||||
.map(integration => {
|
||||
const reader = new HarnessUrlReader(integration);
|
||||
const predicate = (url: URL) => {
|
||||
return url.host === integration.config.host;
|
||||
};
|
||||
return { reader, predicate };
|
||||
});
|
||||
};
|
||||
|
||||
constructor(private readonly integration: HarnessIntegration) {}
|
||||
|
||||
async read(url: string): Promise<Buffer> {
|
||||
const response = await this.readUrl(url);
|
||||
return response.buffer();
|
||||
}
|
||||
|
||||
async readUrl(
|
||||
url: string,
|
||||
options?: ReadUrlOptions,
|
||||
): Promise<ReadUrlResponse> {
|
||||
let response: Response;
|
||||
const blobUrl = getHarnessFileContentsUrl(this.integration.config, url);
|
||||
|
||||
try {
|
||||
response = await fetch(blobUrl, {
|
||||
method: 'GET',
|
||||
...getHarnessRequestOptions(this.integration.config),
|
||||
signal: options?.signal as any,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to read ${blobUrl}, ${e}`);
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
// Harness Code returns the raw content object
|
||||
const jsonResponse = { data: response.body };
|
||||
if (jsonResponse) {
|
||||
return ReadUrlResponseFactory.fromReadable(
|
||||
Readable.from(jsonResponse.data),
|
||||
{
|
||||
etag: response.headers.get('ETag') ?? undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`Unknown json: ${jsonResponse}`);
|
||||
}
|
||||
|
||||
const message = `${url} x ${blobUrl}, ${response.status} ${response.statusText}`;
|
||||
if (response.status === 404) {
|
||||
throw new NotFoundError(message);
|
||||
}
|
||||
|
||||
if (response.status === 304) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
readTree(): Promise<ReadTreeResponse> {
|
||||
throw new Error('HarnessUrlReader readTree not implemented.');
|
||||
}
|
||||
search(): Promise<SearchResponse> {
|
||||
throw new Error('HarnessUrlReader search not implemented.');
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host } = this.integration.config;
|
||||
return `harness{host=${host},authed=${Boolean(
|
||||
this.integration.config.token || this.integration.config.apiKey,
|
||||
)}}`;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
|
||||
import { AwsS3UrlReader } from './AwsS3UrlReader';
|
||||
import { GiteaUrlReader } from './GiteaUrlReader';
|
||||
import { AwsCodeCommitUrlReader } from './AwsCodeCommitUrlReader';
|
||||
import { HarnessUrlReader } from './HarnessUrlReader';
|
||||
|
||||
/**
|
||||
* Creation options for {@link @backstage/backend-plugin-api#UrlReaderService}.
|
||||
@@ -61,7 +62,6 @@ export class UrlReaders {
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config,
|
||||
});
|
||||
|
||||
for (const factory of factories ?? []) {
|
||||
const tuples = factory({ config, logger: logger, treeResponseFactory });
|
||||
|
||||
@@ -94,6 +94,7 @@ export class UrlReaders {
|
||||
GiteaUrlReader.factory,
|
||||
GitlabUrlReader.factory,
|
||||
GoogleGcsUrlReader.factory,
|
||||
HarnessUrlReader.factory,
|
||||
AwsS3UrlReader.factory,
|
||||
AwsCodeCommitUrlReader.factory,
|
||||
FetchUrlReader.factory,
|
||||
|
||||
@@ -22,6 +22,7 @@ export { GerritUrlReader } from './GerritUrlReader';
|
||||
export { GithubUrlReader } from './GithubUrlReader';
|
||||
export { GitlabUrlReader } from './GitlabUrlReader';
|
||||
export { GiteaUrlReader } from './GiteaUrlReader';
|
||||
export { HarnessUrlReader } from './HarnessUrlReader';
|
||||
export { AwsS3UrlReader } from './AwsS3UrlReader';
|
||||
export { FetchUrlReader } from './FetchUrlReader';
|
||||
export { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
|
||||
@@ -22,11 +22,7 @@ import {
|
||||
} from '@backstage/cli-node';
|
||||
import { OptionValues } from 'commander';
|
||||
import fs from 'fs-extra';
|
||||
import {
|
||||
resolve as resolvePath,
|
||||
join as joinPath,
|
||||
relative as relativePath,
|
||||
} from 'path';
|
||||
import { resolve as resolvePath, posix, relative as relativePath } from 'path';
|
||||
import { paths } from '../../lib/paths';
|
||||
|
||||
/**
|
||||
@@ -205,7 +201,7 @@ export function createRepositoryFieldFixer() {
|
||||
const rootDir = rootRepoField.directory || '';
|
||||
|
||||
return (pkg: FixablePackage) => {
|
||||
const expectedPath = joinPath(
|
||||
const expectedPath = posix.join(
|
||||
rootDir,
|
||||
relativePath(paths.targetRoot, pkg.dir),
|
||||
);
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ describe('ExampleFetchComponent', () => {
|
||||
|
||||
// Wait for the table to render
|
||||
const table = await screen.findByRole('table');
|
||||
const nationality = screen.getAllByText('GB')
|
||||
const nationality = screen.getAllByText('GB');
|
||||
// Assert that the table contains the expected user data
|
||||
expect(table).toBeInTheDocument();
|
||||
expect(screen.getByAltText('Carolyn')).toBeInTheDocument();
|
||||
|
||||
@@ -448,6 +448,7 @@ export type GaugeProps = {
|
||||
inverse?: boolean;
|
||||
unit?: string;
|
||||
max?: number;
|
||||
size?: 'normal' | 'small';
|
||||
description?: ReactNode;
|
||||
getColor?: GaugePropsGetColor;
|
||||
};
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2024 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 React from 'react';
|
||||
import { HeaderIconLinkRow } from '../HeaderIconLinkRow';
|
||||
import { IconLinkVerticalProps } from './IconLinkVertical';
|
||||
|
||||
type Props = {
|
||||
links: IconLinkVerticalProps[];
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'Data Display/HeaderIconLinkRow',
|
||||
component: HeaderIconLinkRow,
|
||||
};
|
||||
|
||||
export const Default = (args: Props) => <HeaderIconLinkRow {...args} />;
|
||||
Default.args = {
|
||||
links: [
|
||||
{
|
||||
color: 'primary',
|
||||
disabled: false,
|
||||
href: 'https://google.com',
|
||||
label: 'primary',
|
||||
title: 'title',
|
||||
},
|
||||
{
|
||||
color: 'secondary',
|
||||
disabled: false,
|
||||
href: 'https://google.com',
|
||||
label: 'secondary',
|
||||
title: 'title-2',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -19,6 +19,7 @@ import { makeStyles, useTheme } from '@material-ui/core/styles';
|
||||
import { Circle } from 'rc-progress';
|
||||
import React, { ReactNode, useEffect, useState } from 'react';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/** @public */
|
||||
export type GaugeClassKey =
|
||||
@@ -43,6 +44,9 @@ const useStyles = makeStyles(
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
color: theme.palette.textContrast,
|
||||
},
|
||||
overlaySmall: {
|
||||
fontSize: theme.typography.pxToRem(25),
|
||||
},
|
||||
description: {
|
||||
fontSize: '100%',
|
||||
top: '50%',
|
||||
@@ -68,6 +72,7 @@ export type GaugeProps = {
|
||||
inverse?: boolean;
|
||||
unit?: string;
|
||||
max?: number;
|
||||
size?: 'normal' | 'small';
|
||||
description?: ReactNode;
|
||||
getColor?: GaugePropsGetColor;
|
||||
};
|
||||
@@ -121,7 +126,7 @@ export const getProgressColor: GaugePropsGetColor = ({
|
||||
|
||||
export function Gauge(props: GaugeProps) {
|
||||
const [hoverRef, setHoverRef] = useState<HTMLDivElement | null>(null);
|
||||
const { getColor = getProgressColor } = props;
|
||||
const { getColor = getProgressColor, size = 'normal' } = props;
|
||||
const classes = useStyles(props);
|
||||
const { palette } = useTheme();
|
||||
const { value, fractional, inverse, unit, max, description } = {
|
||||
@@ -165,7 +170,11 @@ export function Gauge(props: GaugeProps) {
|
||||
{description && isHovering ? (
|
||||
<Box className={classes.description}>{description}</Box>
|
||||
) : (
|
||||
<Box className={classes.overlay}>
|
||||
<Box
|
||||
className={classNames(classes.overlay, {
|
||||
[classes.overlaySmall]: size === 'small',
|
||||
})}
|
||||
>
|
||||
{isNaN(value) ? 'N/A' : `${asActual}${unit}`}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -175,6 +175,91 @@ export const InfoMessage = () => (
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export const AlignedBottom = () => (
|
||||
<Wrapper>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
variant="fullHeight"
|
||||
alignGauge="bottom"
|
||||
title="Progress"
|
||||
subheader="With a subheader"
|
||||
progress={0.3}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
variant="fullHeight"
|
||||
alignGauge="bottom"
|
||||
title="Progress"
|
||||
subheader="With a subheader"
|
||||
progress={0.57}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
variant="fullHeight"
|
||||
alignGauge="bottom"
|
||||
title="Progress with longer title"
|
||||
subheader="With a subheader"
|
||||
progress={0.89}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
variant="fullHeight"
|
||||
alignGauge="bottom"
|
||||
title="Progress"
|
||||
subheader="With a subheader"
|
||||
inverse
|
||||
progress={0.2}
|
||||
/>
|
||||
</Grid>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export const Small = () => (
|
||||
<Wrapper>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
variant="fullHeight"
|
||||
alignGauge="bottom"
|
||||
size="small"
|
||||
title="Progress"
|
||||
progress={0.3}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
variant="fullHeight"
|
||||
alignGauge="bottom"
|
||||
size="small"
|
||||
title="Progress"
|
||||
subheader="With a subheader"
|
||||
progress={0.57}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
variant="fullHeight"
|
||||
alignGauge="bottom"
|
||||
size="small"
|
||||
title="Progress, longer title"
|
||||
progress={0.89}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<GaugeCard
|
||||
variant="fullHeight"
|
||||
alignGauge="bottom"
|
||||
size="small"
|
||||
title="Progress"
|
||||
inverse
|
||||
progress={0.2}
|
||||
/>
|
||||
</Grid>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export const HoverMessage = () => (
|
||||
<Wrapper>
|
||||
<Grid item>
|
||||
|
||||
@@ -27,6 +27,8 @@ type Props = {
|
||||
variant?: InfoCardVariants;
|
||||
/** Progress in % specified as decimal, e.g. "0.23" */
|
||||
progress: number;
|
||||
alignGauge?: 'normal' | 'bottom';
|
||||
size?: 'normal' | 'small';
|
||||
description?: ReactNode;
|
||||
icon?: ReactNode;
|
||||
inverse?: boolean;
|
||||
@@ -43,6 +45,10 @@ const useStyles = makeStyles(
|
||||
height: '100%',
|
||||
width: 250,
|
||||
},
|
||||
rootSmall: {
|
||||
height: '100%',
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
{ name: 'BackstageGaugeCard' },
|
||||
);
|
||||
@@ -64,6 +70,8 @@ export function GaugeCard(props: Props) {
|
||||
description,
|
||||
icon,
|
||||
variant,
|
||||
alignGauge = 'normal',
|
||||
size = 'normal',
|
||||
getColor,
|
||||
} = props;
|
||||
|
||||
@@ -75,15 +83,22 @@ export function GaugeCard(props: Props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className={classes.root}>
|
||||
<Box className={size === 'small' ? classes.rootSmall : classes.root}>
|
||||
<InfoCard
|
||||
title={title}
|
||||
subheader={subheader}
|
||||
deepLink={deepLink}
|
||||
variant={variant}
|
||||
alignContent={alignGauge}
|
||||
icon={icon}
|
||||
titleTypographyProps={{
|
||||
...(size === 'small' ? { variant: 'subtitle2' } : undefined),
|
||||
}}
|
||||
subheaderTypographyProps={{
|
||||
...(size === 'small' ? { variant: 'body2' } : undefined),
|
||||
}}
|
||||
>
|
||||
<Gauge {...gaugeProps} />
|
||||
<Gauge {...gaugeProps} size={size} />
|
||||
</InfoCard>
|
||||
</Box>
|
||||
);
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2024 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 React from 'react';
|
||||
import { ResponseErrorPanel } from '../ResponseErrorPanel';
|
||||
import { ErrorPanelProps } from '../ErrorPanel';
|
||||
|
||||
export default {
|
||||
title: 'Data Display/ResponseErrorPanel',
|
||||
component: ResponseErrorPanel,
|
||||
};
|
||||
|
||||
export const Default = (args: ErrorPanelProps) => (
|
||||
<ResponseErrorPanel {...args} />
|
||||
);
|
||||
Default.args = {
|
||||
error: new Error('Error message from error object'),
|
||||
defaultExpanded: false,
|
||||
};
|
||||
|
||||
export const WithTitle = (args: ErrorPanelProps) => (
|
||||
<ResponseErrorPanel {...args} />
|
||||
);
|
||||
WithTitle.args = {
|
||||
error: new Error('test'),
|
||||
defaultExpanded: false,
|
||||
title: 'Title prop is passed',
|
||||
};
|
||||
+14
-16
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
* Copyright 2024 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.
|
||||
@@ -14,20 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
AnalyzeLocationRequest,
|
||||
AnalyzeLocationResponse,
|
||||
} from '@backstage/plugin-catalog-common';
|
||||
import React from 'react';
|
||||
import { BottomLink } from '../BottomLink';
|
||||
|
||||
/** @public */
|
||||
export type LocationAnalyzer = {
|
||||
/**
|
||||
* Generates an entity configuration for given git repository. It's used for
|
||||
* importing new component to the backstage app.
|
||||
*
|
||||
* @param location - Git repository to analyze and generate config for.
|
||||
*/
|
||||
analyzeLocation(
|
||||
location: AnalyzeLocationRequest,
|
||||
): Promise<AnalyzeLocationResponse>;
|
||||
export default {
|
||||
title: 'Layout/BottomLink',
|
||||
component: BottomLink,
|
||||
};
|
||||
|
||||
export const Default = (args: { link: string; title: string }) => (
|
||||
<BottomLink {...args} />
|
||||
);
|
||||
Default.args = {
|
||||
link: 'https://google.com',
|
||||
title: 'This is bottom link',
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2024 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 React, { ReactNode } from 'react';
|
||||
import { ContentHeader } from '../ContentHeader';
|
||||
|
||||
export default {
|
||||
title: 'Layout/ContentHeader',
|
||||
component: ContentHeader,
|
||||
};
|
||||
|
||||
type ContentHeaderProps = {
|
||||
title?: string;
|
||||
titleComponent?: ReactNode;
|
||||
description?: string;
|
||||
textAlign?: 'left' | 'right' | 'center';
|
||||
};
|
||||
|
||||
export const Default = (args: ContentHeaderProps) => (
|
||||
<ContentHeader {...args}>
|
||||
<div>Child of Content Header</div>
|
||||
</ContentHeader>
|
||||
);
|
||||
Default.args = {
|
||||
title: 'This is Content Header default aligned',
|
||||
description: 'This is description',
|
||||
};
|
||||
|
||||
export const Left = (args: ContentHeaderProps) => (
|
||||
<ContentHeader {...args}>
|
||||
<div>Child of Content Header</div>
|
||||
</ContentHeader>
|
||||
);
|
||||
Left.args = {
|
||||
title: 'This is Content Header left aligned',
|
||||
description: 'This is description',
|
||||
textAlign: 'left',
|
||||
};
|
||||
|
||||
export const Right = (args: ContentHeaderProps) => (
|
||||
<ContentHeader {...args}>
|
||||
<div>Child of Content Header</div>
|
||||
</ContentHeader>
|
||||
);
|
||||
Right.args = {
|
||||
title: 'This is Content Header right aligned',
|
||||
description: 'This is description',
|
||||
textAlign: 'right',
|
||||
};
|
||||
|
||||
export const Center = (args: ContentHeaderProps) => (
|
||||
<ContentHeader {...args}>
|
||||
<div>Child of Content Header</div>
|
||||
</ContentHeader>
|
||||
);
|
||||
Center.args = {
|
||||
title: 'This is Content Header center aligned',
|
||||
description: 'This is description',
|
||||
textAlign: 'center',
|
||||
};
|
||||
@@ -43,6 +43,10 @@ const useStyles = makeStyles(
|
||||
paddingBottom: 0,
|
||||
},
|
||||
},
|
||||
contentAlignBottom: {
|
||||
display: 'flex',
|
||||
alignItems: 'self-end',
|
||||
},
|
||||
header: {
|
||||
padding: theme.spacing(2, 2, 2, 2.5),
|
||||
},
|
||||
@@ -138,6 +142,7 @@ export type Props = {
|
||||
slackChannel?: string;
|
||||
errorBoundaryProps?: ErrorBoundaryProps;
|
||||
variant?: InfoCardVariants;
|
||||
alignContent?: 'normal' | 'bottom';
|
||||
children?: ReactNode;
|
||||
headerStyle?: object;
|
||||
headerProps?: CardHeaderProps;
|
||||
@@ -150,6 +155,7 @@ export type Props = {
|
||||
className?: string;
|
||||
noPadding?: boolean;
|
||||
titleTypographyProps?: object;
|
||||
subheaderTypographyProps?: object;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -167,6 +173,7 @@ export function InfoCard(props: Props): JSX.Element {
|
||||
slackChannel,
|
||||
errorBoundaryProps,
|
||||
variant,
|
||||
alignContent = 'normal',
|
||||
children,
|
||||
headerStyle,
|
||||
headerProps,
|
||||
@@ -179,6 +186,7 @@ export function InfoCard(props: Props): JSX.Element {
|
||||
className,
|
||||
noPadding,
|
||||
titleTypographyProps,
|
||||
subheaderTypographyProps,
|
||||
} = props;
|
||||
const classes = useStyles();
|
||||
/**
|
||||
@@ -209,10 +217,7 @@ export function InfoCard(props: Props): JSX.Element {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes.headerSubheader}
|
||||
data-testid="info-card-subheader"
|
||||
>
|
||||
<div data-testid="info-card-subheader">
|
||||
{subheader && <div className={classes.subheader}>{subheader}</div>}
|
||||
{icon}
|
||||
</div>
|
||||
@@ -228,7 +233,7 @@ export function InfoCard(props: Props): JSX.Element {
|
||||
{title && (
|
||||
<CardHeader
|
||||
classes={{
|
||||
root: classes.header,
|
||||
root: classNames(classes.header),
|
||||
title: classes.headerTitle,
|
||||
subheader: classes.headerSubheader,
|
||||
avatar: classes.headerAvatar,
|
||||
@@ -240,6 +245,7 @@ export function InfoCard(props: Props): JSX.Element {
|
||||
action={action}
|
||||
style={{ ...headerStyle }}
|
||||
titleTypographyProps={titleTypographyProps}
|
||||
subheaderTypographyProps={subheaderTypographyProps}
|
||||
{...headerProps}
|
||||
/>
|
||||
)}
|
||||
@@ -250,6 +256,7 @@ export function InfoCard(props: Props): JSX.Element {
|
||||
<CardContent
|
||||
className={classNames(cardClassName, {
|
||||
[classes.noPadding]: noPadding,
|
||||
[classes.contentAlignBottom]: alignContent === 'bottom',
|
||||
})}
|
||||
style={calculatedCardStyle}
|
||||
>
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/fs-extra": "^11.0.0",
|
||||
"@types/node": "^18.17.8",
|
||||
"@types/puppeteer": "^7.0.0",
|
||||
"nodemon": "^3.0.1"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
|
||||
@@ -513,6 +513,17 @@ export function getGitLabRequestOptions(config: GitLabIntegrationConfig): {
|
||||
headers: Record<string, string>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function getHarnessFileContentsUrl(
|
||||
config: HarnessIntegrationConfig,
|
||||
url: string,
|
||||
): string;
|
||||
|
||||
// @public
|
||||
export function getHarnessRequestOptions(config: HarnessIntegrationConfig): {
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class GiteaIntegration implements ScmIntegration {
|
||||
constructor(config: GiteaIntegrationConfig);
|
||||
@@ -674,6 +685,34 @@ export type GoogleGcsIntegrationConfig = {
|
||||
privateKey?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class HarnessIntegration implements ScmIntegration {
|
||||
constructor(config: HarnessIntegrationConfig);
|
||||
// (undocumented)
|
||||
readonly config: HarnessIntegrationConfig;
|
||||
// (undocumented)
|
||||
static factory: ScmIntegrationsFactory<HarnessIntegration>;
|
||||
// (undocumented)
|
||||
resolveEditUrl(url: string): string;
|
||||
// (undocumented)
|
||||
resolveUrl(options: {
|
||||
url: string;
|
||||
base: string;
|
||||
lineNumber?: number | undefined;
|
||||
}): string;
|
||||
// (undocumented)
|
||||
get title(): string;
|
||||
// (undocumented)
|
||||
get type(): string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type HarnessIntegrationConfig = {
|
||||
host: string;
|
||||
token?: string;
|
||||
apiKey?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface IntegrationsByType {
|
||||
// (undocumented)
|
||||
@@ -696,6 +735,8 @@ export interface IntegrationsByType {
|
||||
github: ScmIntegrationsGroup<GithubIntegration>;
|
||||
// (undocumented)
|
||||
gitlab: ScmIntegrationsGroup<GitLabIntegration>;
|
||||
// (undocumented)
|
||||
harness: ScmIntegrationsGroup<HarnessIntegration>;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -839,6 +880,9 @@ export function readGoogleGcsIntegrationConfig(
|
||||
config: Config,
|
||||
): GoogleGcsIntegrationConfig;
|
||||
|
||||
// @public
|
||||
export function readHarnessConfig(config: Config): HarnessIntegrationConfig;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const replaceGitHubUrlType: typeof replaceGithubUrlType;
|
||||
|
||||
@@ -889,6 +933,8 @@ export interface ScmIntegrationRegistry
|
||||
github: ScmIntegrationsGroup<GithubIntegration>;
|
||||
// (undocumented)
|
||||
gitlab: ScmIntegrationsGroup<GitLabIntegration>;
|
||||
// (undocumented)
|
||||
harness: ScmIntegrationsGroup<HarnessIntegration>;
|
||||
resolveEditUrl(url: string): string;
|
||||
resolveUrl(options: {
|
||||
url: string;
|
||||
@@ -927,6 +973,8 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
// (undocumented)
|
||||
get gitlab(): ScmIntegrationsGroup<GitLabIntegration>;
|
||||
// (undocumented)
|
||||
get harness(): ScmIntegrationsGroup<HarnessIntegration>;
|
||||
// (undocumented)
|
||||
list(): ScmIntegration[];
|
||||
// (undocumented)
|
||||
resolveEditUrl(url: string): string;
|
||||
|
||||
Vendored
+18
@@ -345,5 +345,23 @@ export interface Config {
|
||||
*/
|
||||
password?: string;
|
||||
}>;
|
||||
/** Integration configuration for Harness Code */
|
||||
harness?: Array<{
|
||||
/**
|
||||
* The hostname of the given Harness Code instance
|
||||
* @visibility frontend
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* The apikey to use for authenticated requests.
|
||||
* @visibility secret
|
||||
*/
|
||||
apiKey?: string;
|
||||
/**
|
||||
* Harness Code token used to authenticate requests. This can be either a generated access token.
|
||||
* @visibility secret
|
||||
*/
|
||||
token?: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import { ScmIntegrations } from './ScmIntegrations';
|
||||
import { GiteaIntegration, GiteaIntegrationConfig } from './gitea';
|
||||
import { AwsCodeCommitIntegration } from './awsCodeCommit/AwsCodeCommitIntegration';
|
||||
import { AwsCodeCommitIntegrationConfig } from './awsCodeCommit';
|
||||
import { HarnessIntegration, HarnessIntegrationConfig } from './harness';
|
||||
|
||||
describe('ScmIntegrations', () => {
|
||||
const awsS3 = new AwsS3Integration({
|
||||
@@ -80,6 +81,10 @@ describe('ScmIntegrations', () => {
|
||||
host: 'gitea.local',
|
||||
} as GiteaIntegrationConfig);
|
||||
|
||||
const harness = new HarnessIntegration({
|
||||
host: 'harness.local',
|
||||
} as HarnessIntegrationConfig);
|
||||
|
||||
const i = new ScmIntegrations({
|
||||
awsS3: basicIntegrations([awsS3], item => item.config.host),
|
||||
awsCodeCommit: basicIntegrations([awsCodeCommit], item => item.config.host),
|
||||
@@ -94,6 +99,7 @@ describe('ScmIntegrations', () => {
|
||||
github: basicIntegrations([github], item => item.config.host),
|
||||
gitlab: basicIntegrations([gitlab], item => item.config.host),
|
||||
gitea: basicIntegrations([gitea], item => item.config.host),
|
||||
harness: basicIntegrations([harness], item => item.config.host),
|
||||
});
|
||||
|
||||
it('can get the specifics', () => {
|
||||
@@ -113,6 +119,7 @@ describe('ScmIntegrations', () => {
|
||||
expect(i.github.byUrl('https://github.local')).toBe(github);
|
||||
expect(i.gitlab.byUrl('https://gitlab.local')).toBe(gitlab);
|
||||
expect(i.gitea.byUrl('https://gitea.local')).toBe(gitea);
|
||||
expect(i.harness.byUrl('https://harness.local')).toBe(harness);
|
||||
});
|
||||
|
||||
it('can list', () => {
|
||||
@@ -128,6 +135,7 @@ describe('ScmIntegrations', () => {
|
||||
github,
|
||||
gitlab,
|
||||
gitea,
|
||||
harness,
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -143,6 +151,7 @@ describe('ScmIntegrations', () => {
|
||||
expect(i.byUrl('https://github.local')).toBe(github);
|
||||
expect(i.byUrl('https://gitlab.local')).toBe(gitlab);
|
||||
expect(i.byUrl('https://gitea.local')).toBe(gitea);
|
||||
expect(i.byUrl('https://harness.local')).toBe(harness);
|
||||
|
||||
expect(i.byHost('awss3.local')).toBe(awsS3);
|
||||
expect(i.byHost('awscodecommit.local')).toBe(awsCodeCommit);
|
||||
|
||||
@@ -28,6 +28,7 @@ import { defaultScmResolveUrl } from './helpers';
|
||||
import { ScmIntegration, ScmIntegrationsGroup } from './types';
|
||||
import { ScmIntegrationRegistry } from './registry';
|
||||
import { GiteaIntegration } from './gitea';
|
||||
import { HarnessIntegration } from './harness/HarnessIntegration';
|
||||
|
||||
/**
|
||||
* The set of supported integrations.
|
||||
@@ -48,6 +49,7 @@ export interface IntegrationsByType {
|
||||
github: ScmIntegrationsGroup<GithubIntegration>;
|
||||
gitlab: ScmIntegrationsGroup<GitLabIntegration>;
|
||||
gitea: ScmIntegrationsGroup<GiteaIntegration>;
|
||||
harness: ScmIntegrationsGroup<HarnessIntegration>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,6 +72,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
github: GithubIntegration.factory({ config }),
|
||||
gitlab: GitLabIntegration.factory({ config }),
|
||||
gitea: GiteaIntegration.factory({ config }),
|
||||
harness: HarnessIntegration.factory({ config }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,6 +123,10 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
return this.byType.gitea;
|
||||
}
|
||||
|
||||
get harness(): ScmIntegrationsGroup<HarnessIntegration> {
|
||||
return this.byType.harness;
|
||||
}
|
||||
|
||||
list(): ScmIntegration[] {
|
||||
return Object.values(this.byType).flatMap(
|
||||
i => i.list() as ScmIntegration[],
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2024 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 { HarnessIntegration } from './HarnessIntegration';
|
||||
|
||||
describe('HarnessIntegration', () => {
|
||||
it('has a working factory', () => {
|
||||
const integrations = HarnessIntegration.factory({
|
||||
config: new ConfigReader({
|
||||
integrations: {
|
||||
harness: [
|
||||
{
|
||||
host: 'app.harness.io',
|
||||
token: '1234',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(integrations.list().length).toBe(1);
|
||||
expect(integrations.list()[0].config.host).toBe('app.harness.io');
|
||||
});
|
||||
|
||||
it('returns the basics', () => {
|
||||
const integration = new HarnessIntegration({
|
||||
host: 'app.harness.io',
|
||||
});
|
||||
expect(integration.type).toBe('harness');
|
||||
expect(integration.title).toBe('app.harness.io');
|
||||
});
|
||||
|
||||
describe('resolveUrl', () => {
|
||||
it('works for valid urls, ignoring line number', () => {
|
||||
const integration = new HarnessIntegration({
|
||||
host: 'app.harness.io',
|
||||
});
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: 'https://app.harness.io/catalog-info.yaml',
|
||||
base: 'https://app.harness.io/catalog-info.yaml',
|
||||
lineNumber: 9,
|
||||
}),
|
||||
).toBe('https://app.harness.io/catalog-info.yaml');
|
||||
});
|
||||
|
||||
it('handles line numbers', () => {
|
||||
const integration = new HarnessIntegration({
|
||||
host: 'app.harness.io',
|
||||
});
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: '',
|
||||
base: 'https://app.harness.io/catalog-info.yaml#4',
|
||||
lineNumber: 9,
|
||||
}),
|
||||
).toBe('https://app.harness.io/catalog-info.yaml#L9');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolves with a relative url', () => {
|
||||
it('works for valid urls', () => {
|
||||
const integration = new HarnessIntegration({
|
||||
host: 'app.harness.io',
|
||||
});
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: './skeleton',
|
||||
base: 'https://app.harness.io/git/plugins/repo/+/refs/heads/master/template.yaml',
|
||||
}),
|
||||
).toBe(
|
||||
'https://app.harness.io/git/plugins/repo/+/refs/heads/master/skeleton',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolves with an absolute url', () => {
|
||||
it('works for valid urls', () => {
|
||||
const integration = new HarnessIntegration({
|
||||
host: 'app.harness.io',
|
||||
});
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: '/catalog-info.yaml',
|
||||
base: 'https://app.harness.io/git/repo/+/refs/heads/master/',
|
||||
}),
|
||||
).toBe(
|
||||
'https://app.harness.io/git/repo/+/refs/heads/master/catalog-info.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('resolve edit URL', () => {
|
||||
const integration = new HarnessIntegration({
|
||||
host: 'app.harness.io',
|
||||
});
|
||||
|
||||
expect(
|
||||
integration.resolveEditUrl(
|
||||
'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/edit/refMain/~/all-apis.yaml',
|
||||
),
|
||||
).toBe(
|
||||
'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/edit/all-apis.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2024 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 { basicIntegrations, defaultScmResolveUrl } from '../helpers';
|
||||
import { ScmIntegration, ScmIntegrationsFactory } from '../types';
|
||||
import { HarnessIntegrationConfig, readHarnessConfig } from './config';
|
||||
import { getHarnessEditContentsUrl } from './core';
|
||||
|
||||
/**
|
||||
* A Harness Code based integration.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class HarnessIntegration implements ScmIntegration {
|
||||
static factory: ScmIntegrationsFactory<HarnessIntegration> = ({ config }) => {
|
||||
const configs = config.getOptionalConfigArray('integrations.harness') ?? [];
|
||||
const harnessConfigs = configs.map(c => readHarnessConfig(c));
|
||||
|
||||
return basicIntegrations(
|
||||
harnessConfigs.map(c => new HarnessIntegration(c)),
|
||||
(harness: HarnessIntegration) => harness.config.host,
|
||||
);
|
||||
};
|
||||
|
||||
constructor(readonly config: HarnessIntegrationConfig) {}
|
||||
|
||||
get type(): string {
|
||||
return 'harness';
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this.config.host;
|
||||
}
|
||||
|
||||
resolveUrl(options: {
|
||||
url: string;
|
||||
base: string;
|
||||
lineNumber?: number | undefined;
|
||||
}): string {
|
||||
return defaultScmResolveUrl(options);
|
||||
}
|
||||
|
||||
resolveEditUrl(url: string): string {
|
||||
return getHarnessEditContentsUrl(this.config, url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2024 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 { Config, ConfigReader } from '@backstage/config';
|
||||
import { loadConfigSchema } from '@backstage/config-loader';
|
||||
import { HarnessIntegrationConfig, readHarnessConfig } from './config';
|
||||
|
||||
describe('readHarnessConfig', () => {
|
||||
function buildConfig(data: Partial<HarnessIntegrationConfig>): Config {
|
||||
return new ConfigReader(data);
|
||||
}
|
||||
|
||||
async function buildFrontendConfig(
|
||||
data: Partial<HarnessIntegrationConfig>,
|
||||
): Promise<Config> {
|
||||
const fullSchema = await loadConfigSchema({
|
||||
dependencies: ['@backstage/integration'],
|
||||
});
|
||||
const serializedSchema = fullSchema.serialize() as {
|
||||
schemas: { value: { properties?: { integrations?: object } } }[];
|
||||
};
|
||||
const schema = await loadConfigSchema({
|
||||
serialized: {
|
||||
...serializedSchema, // only include schemas that apply to integrations
|
||||
schemas: serializedSchema.schemas.filter(
|
||||
s => s.value?.properties?.integrations,
|
||||
),
|
||||
},
|
||||
});
|
||||
const processed = schema.process(
|
||||
[{ data: { integrations: { harness: [data] } }, context: 'app' }],
|
||||
{ visibility: ['frontend'] },
|
||||
);
|
||||
return new ConfigReader((processed[0].data as any).integrations.harness[0]);
|
||||
}
|
||||
|
||||
it('reads all values', () => {
|
||||
const output = readHarnessConfig(
|
||||
buildConfig({
|
||||
host: 'a.com',
|
||||
token: 'p',
|
||||
apiKey: 'a',
|
||||
}),
|
||||
);
|
||||
expect(output).toEqual({
|
||||
host: 'a.com',
|
||||
token: 'p',
|
||||
apiKey: 'a',
|
||||
});
|
||||
});
|
||||
|
||||
it('can create a default value if the API base URL is missing', () => {
|
||||
const output = readHarnessConfig(
|
||||
buildConfig({
|
||||
host: 'a.com',
|
||||
}),
|
||||
);
|
||||
expect(output).toEqual({
|
||||
host: 'a.com',
|
||||
token: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects funky configs', () => {
|
||||
const valid: any = {
|
||||
host: 'a.com',
|
||||
};
|
||||
expect(() => readHarnessConfig(buildConfig({ ...valid, host: 2 }))).toThrow(
|
||||
/host/,
|
||||
);
|
||||
});
|
||||
|
||||
it('works on the frontend', async () => {
|
||||
expect(
|
||||
readHarnessConfig(
|
||||
await buildFrontendConfig({
|
||||
host: 'a.com',
|
||||
token: 'p',
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
host: 'a.com',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2024 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 { Config } from '@backstage/config';
|
||||
import { isValidHost } from '../helpers';
|
||||
|
||||
/**
|
||||
* The configuration for a single Harness integration.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type HarnessIntegrationConfig = {
|
||||
/**
|
||||
* The host of the target that this matches on, e.g. "app.harness.io"
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* The password or http token to use for authentication.
|
||||
*/
|
||||
token?: string;
|
||||
/**
|
||||
* The API key to use for authentication.
|
||||
*/
|
||||
apiKey?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a location config block for use in HarnessIntegration
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function readHarnessConfig(config: Config): HarnessIntegrationConfig {
|
||||
const host = config.getString('host');
|
||||
const token = config.getOptionalString('token');
|
||||
const apiKey = config.getOptionalString('apiKey');
|
||||
|
||||
if (!isValidHost(host)) {
|
||||
throw new Error(
|
||||
`Invalid Harness Code integration config, '${host}' is not a valid host`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
host,
|
||||
apiKey,
|
||||
token,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 { setupServer } from 'msw/node';
|
||||
import { setupRequestMockHandlers } from '../helpers';
|
||||
import { HarnessIntegrationConfig } from './config';
|
||||
import {
|
||||
getHarnessEditContentsUrl,
|
||||
getHarnessFileContentsUrl,
|
||||
getHarnessRequestOptions,
|
||||
} from './core';
|
||||
|
||||
describe('Harness code core', () => {
|
||||
const worker = setupServer();
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
describe('getHarnessFileContentsUrl', () => {
|
||||
it('can create an url from arguments', () => {
|
||||
const config: HarnessIntegrationConfig = {
|
||||
host: 'app.harness.io',
|
||||
};
|
||||
expect(
|
||||
getHarnessFileContentsUrl(
|
||||
config,
|
||||
'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml',
|
||||
),
|
||||
).toEqual(
|
||||
'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/raw/all-apis.yaml?routingId=accountId&git_ref=refMain',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHarnessEditContentsUrl', () => {
|
||||
it('can create an url from arguments', () => {
|
||||
const config: HarnessIntegrationConfig = {
|
||||
host: 'app.harness.io',
|
||||
};
|
||||
expect(
|
||||
getHarnessEditContentsUrl(
|
||||
config,
|
||||
'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/edit/refMain/~/all-apis.yaml',
|
||||
),
|
||||
).toEqual(
|
||||
'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/edit/all-apis.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHarnessRequestOptions', () => {
|
||||
it('adds token header when only a token is specified', () => {
|
||||
const authRequest: HarnessIntegrationConfig = {
|
||||
host: 'app.harness.io',
|
||||
token: 'P',
|
||||
};
|
||||
const anonymousRequest: HarnessIntegrationConfig = {
|
||||
host: 'app.harness.io',
|
||||
};
|
||||
expect(
|
||||
(getHarnessRequestOptions(authRequest).headers as any).Authorization,
|
||||
).toEqual('Bearer P');
|
||||
expect(getHarnessRequestOptions(anonymousRequest).headers).toStrictEqual(
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
it('adds basic auth when apikey and token are specified', () => {
|
||||
const authRequest: HarnessIntegrationConfig = {
|
||||
host: 'app.harness.io',
|
||||
token: 'P',
|
||||
apiKey: 'a',
|
||||
};
|
||||
|
||||
expect(
|
||||
(getHarnessRequestOptions(authRequest).headers as any)['x-api-key'],
|
||||
).toEqual('a');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2024 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 { HarnessIntegrationConfig } from './config';
|
||||
|
||||
/**
|
||||
* Given a URL pointing to a file, returns a URL
|
||||
* for editing the contents of the data.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Converts
|
||||
* from: https://app.harness.io/a/b/src/branchname/path/to/c.yaml
|
||||
* or: https://app.harness.io/a/b/_edit/branchname/path/to/c.yaml
|
||||
*
|
||||
* @param url - A URL pointing to a file
|
||||
* @param config - The relevant provider config
|
||||
* @public
|
||||
*/
|
||||
export function getHarnessEditContentsUrl(
|
||||
config: HarnessIntegrationConfig,
|
||||
url: string,
|
||||
) {
|
||||
try {
|
||||
const baseUrl = `https://${config.host}`;
|
||||
const [
|
||||
_blank,
|
||||
_ng,
|
||||
_account,
|
||||
accountId,
|
||||
_module,
|
||||
_moduleName,
|
||||
_org,
|
||||
orgName,
|
||||
_projects,
|
||||
projectName,
|
||||
_repos,
|
||||
repoName,
|
||||
_files,
|
||||
_ref,
|
||||
_branch,
|
||||
...path
|
||||
] = url.replace(baseUrl, '').split('/');
|
||||
const pathWithoutSlash = path.join('/').replace(/^\//, '');
|
||||
return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/edit/${pathWithoutSlash}`;
|
||||
} catch (e) {
|
||||
throw new Error(`Incorrect URL: ${url}, ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a file path URL,
|
||||
* it returns an API URL which returns the contents of the file.
|
||||
* @remarks
|
||||
*
|
||||
* Converts
|
||||
* from: https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml
|
||||
* to: https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain
|
||||
*
|
||||
* @param url - A URL pointing to a file
|
||||
* @param config - The relevant provider config
|
||||
* @public
|
||||
*/
|
||||
export function getHarnessFileContentsUrl(
|
||||
config: HarnessIntegrationConfig,
|
||||
url: string,
|
||||
) {
|
||||
try {
|
||||
const baseUrl = `https://${config.host}`;
|
||||
const [
|
||||
_blank,
|
||||
_ng,
|
||||
_account,
|
||||
accountId,
|
||||
_module,
|
||||
_moduleName,
|
||||
_org,
|
||||
orgName,
|
||||
_projects,
|
||||
projectName,
|
||||
_repos,
|
||||
repoName,
|
||||
_files,
|
||||
_ref,
|
||||
_branch,
|
||||
...path
|
||||
] = url.replace(baseUrl, '').split('/');
|
||||
const urlParts = url.replace(baseUrl, '').split('/');
|
||||
const refAndPath = urlParts.slice(13);
|
||||
const refIndex = refAndPath.findIndex(item => item === '~');
|
||||
const refString = refAndPath.slice(0, refIndex);
|
||||
const pathWithoutSlash = path.join('/').replace(/^\//, '');
|
||||
return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/raw/${pathWithoutSlash}?routingId=${accountId}&git_ref=${refString}`;
|
||||
} catch (e) {
|
||||
throw new Error(`Incorrect URL: ${url}, ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return request headers for a Harness Code provider.
|
||||
*
|
||||
* @param config - A Harness Code provider config
|
||||
* @public
|
||||
*/
|
||||
export function getHarnessRequestOptions(config: HarnessIntegrationConfig): {
|
||||
headers?: Record<string, string>;
|
||||
} {
|
||||
const headers: Record<string, string> = {};
|
||||
const { token, apiKey } = config;
|
||||
|
||||
if (apiKey) {
|
||||
headers['x-api-key'] = apiKey;
|
||||
} else if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
export { HarnessIntegration } from './HarnessIntegration';
|
||||
export { getHarnessRequestOptions, getHarnessFileContentsUrl } from './core';
|
||||
export { readHarnessConfig } from './config';
|
||||
export type { HarnessIntegrationConfig } from './config';
|
||||
@@ -31,6 +31,7 @@ export * from './gitea';
|
||||
export * from './github';
|
||||
export * from './gitlab';
|
||||
export * from './googleGcs';
|
||||
export * from './harness';
|
||||
export { defaultScmResolveUrl } from './helpers';
|
||||
export { ScmIntegrations } from './ScmIntegrations';
|
||||
export type { IntegrationsByType } from './ScmIntegrations';
|
||||
|
||||
@@ -25,6 +25,7 @@ import { GerritIntegration } from './gerrit/GerritIntegration';
|
||||
import { GithubIntegration } from './github/GithubIntegration';
|
||||
import { GitLabIntegration } from './gitlab/GitLabIntegration';
|
||||
import { GiteaIntegration } from './gitea/GiteaIntegration';
|
||||
import { HarnessIntegration } from './harness/HarnessIntegration';
|
||||
|
||||
/**
|
||||
* Holds all registered SCM integrations, of all types.
|
||||
@@ -46,6 +47,7 @@ export interface ScmIntegrationRegistry
|
||||
github: ScmIntegrationsGroup<GithubIntegration>;
|
||||
gitlab: ScmIntegrationsGroup<GitLabIntegration>;
|
||||
gitea: ScmIntegrationsGroup<GiteaIntegration>;
|
||||
harness: ScmIntegrationsGroup<HarnessIntegration>;
|
||||
/**
|
||||
* Resolves an absolute or relative URL in relation to a base URL.
|
||||
*
|
||||
|
||||
+12
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
* Copyright 2024 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.
|
||||
@@ -14,4 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type { LocationAnalyzer } from './types';
|
||||
export interface Config {
|
||||
auth?: {
|
||||
providers?: {
|
||||
/** @visibility frontend */
|
||||
awsalb?: {
|
||||
issuer?: string;
|
||||
region: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
export interface Config {
|
||||
auth?: {
|
||||
providers?: {
|
||||
/** @visibility frontend */
|
||||
github?: {
|
||||
[authEnv: string]: {
|
||||
clientId: string;
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
clientSecret: string;
|
||||
callbackUrl?: string;
|
||||
enterpriseInstanceUrl?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend-module-github-provider",
|
||||
"description": "The github-provider backend module for the auth plugin.",
|
||||
"version": "0.1.15-next.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"description": "The github-provider backend module for the auth plugin.",
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
@@ -15,17 +15,21 @@
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/auth-backend-module-github-provider"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"start": "backstage-cli package start",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
@@ -39,7 +43,5 @@
|
||||
"@backstage/plugin-auth-backend": "workspace:^",
|
||||
"supertest": "^6.3.3"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
|
||||
Vendored
-42
@@ -89,29 +89,6 @@ export interface Config {
|
||||
* @additionalProperties true
|
||||
*/
|
||||
providers?: {
|
||||
/** @visibility frontend */
|
||||
google?: {
|
||||
[authEnv: string]: {
|
||||
clientId: string;
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
clientSecret: string;
|
||||
callbackUrl?: string;
|
||||
};
|
||||
};
|
||||
/** @visibility frontend */
|
||||
github?: {
|
||||
[authEnv: string]: {
|
||||
clientId: string;
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
clientSecret: string;
|
||||
callbackUrl?: string;
|
||||
enterpriseInstanceUrl?: string;
|
||||
};
|
||||
};
|
||||
/** @visibility frontend */
|
||||
saml?: {
|
||||
entryPoint: string;
|
||||
@@ -137,20 +114,6 @@ export interface Config {
|
||||
acceptedClockSkewMs?: number;
|
||||
};
|
||||
/** @visibility frontend */
|
||||
oauth2?: {
|
||||
[authEnv: string]: {
|
||||
clientId: string;
|
||||
/**
|
||||
* @visibility secret
|
||||
*/
|
||||
clientSecret: string;
|
||||
authorizationUrl: string;
|
||||
tokenUrl: string;
|
||||
scope?: string;
|
||||
disableRefresh?: boolean;
|
||||
};
|
||||
};
|
||||
/** @visibility frontend */
|
||||
auth0?: {
|
||||
[authEnv: string]: {
|
||||
clientId: string;
|
||||
@@ -177,11 +140,6 @@ export interface Config {
|
||||
callbackUrl?: string;
|
||||
};
|
||||
};
|
||||
/** @visibility frontend */
|
||||
awsalb?: {
|
||||
iss?: string;
|
||||
region: string;
|
||||
};
|
||||
/**
|
||||
* The backstage token expiration.
|
||||
*/
|
||||
|
||||
@@ -37,7 +37,8 @@ describe('githubCatalogModule', () => {
|
||||
};
|
||||
|
||||
const analysisExtensionPoint = {
|
||||
addLocationAnalyzer: jest.fn(),
|
||||
setLocationAnalyzer: jest.fn(),
|
||||
addScmLocationAnalyzer: jest.fn(),
|
||||
};
|
||||
|
||||
const runner = jest.fn();
|
||||
@@ -81,7 +82,7 @@ describe('githubCatalogModule', () => {
|
||||
'github-provider:default',
|
||||
);
|
||||
expect(runner).not.toHaveBeenCalled();
|
||||
expect(analysisExtensionPoint.addLocationAnalyzer).toHaveBeenCalledWith(
|
||||
expect(analysisExtensionPoint.addScmLocationAnalyzer).toHaveBeenCalledWith(
|
||||
expect.any(GithubLocationAnalyzer),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ export const githubCatalogModule = createBackendModule({
|
||||
discovery,
|
||||
auth,
|
||||
}) {
|
||||
analyzers.addLocationAnalyzer(
|
||||
analyzers.addScmLocationAnalyzer(
|
||||
new GithubLocationAnalyzer({
|
||||
discovery,
|
||||
config,
|
||||
|
||||
@@ -41,6 +41,7 @@ import { EntityRelationSpec as EntityRelationSpec_2 } from '@backstage/plugin-ca
|
||||
import { EventBroker } from '@backstage/plugin-events-node';
|
||||
import { GetEntitiesRequest } from '@backstage/catalog-client';
|
||||
import { HttpAuthService } from '@backstage/backend-plugin-api';
|
||||
import { LocationAnalyzer as LocationAnalyzer_2 } from '@backstage/plugin-catalog-node';
|
||||
import { LocationSpec as LocationSpec_2 } from '@backstage/plugin-catalog-common';
|
||||
import { locationSpecToLocationEntity as locationSpecToLocationEntity_2 } from '@backstage/plugin-catalog-node';
|
||||
import { locationSpecToMetadataName as locationSpecToMetadataName_2 } from '@backstage/plugin-catalog-node';
|
||||
@@ -167,7 +168,7 @@ export class CatalogBuilder {
|
||||
setEntityDataParser(parser: CatalogProcessorParser_2): CatalogBuilder;
|
||||
setEventBroker(broker: EventBroker): CatalogBuilder;
|
||||
setFieldFormatValidators(validators: Partial<Validators>): CatalogBuilder;
|
||||
setLocationAnalyzer(locationAnalyzer: LocationAnalyzer): CatalogBuilder;
|
||||
setLocationAnalyzer(locationAnalyzer: LocationAnalyzer_2): CatalogBuilder;
|
||||
setPlaceholderResolver(
|
||||
key: string,
|
||||
resolver: PlaceholderResolver_2,
|
||||
@@ -359,12 +360,8 @@ export class FileReaderProcessor implements CatalogProcessor_2 {
|
||||
): Promise<boolean>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type LocationAnalyzer = {
|
||||
analyzeLocation(
|
||||
location: AnalyzeLocationRequest_2,
|
||||
): Promise<AnalyzeLocationResponse_2>;
|
||||
};
|
||||
// @public @deprecated (undocumented)
|
||||
export type LocationAnalyzer = LocationAnalyzer_2;
|
||||
|
||||
// @public @deprecated
|
||||
export class LocationEntityProcessor implements CatalogProcessor_2 {
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
type PlaceholderResolverParams as _PlaceholderResolverParams,
|
||||
type PlaceholderResolverRead as _PlaceholderResolverRead,
|
||||
type PlaceholderResolverResolveUrl as _PlaceholderResolverResolveUrl,
|
||||
type LocationAnalyzer as _LocationAnalyzer,
|
||||
type ScmLocationAnalyzer as _ScmLocationAnalyzer,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import {
|
||||
@@ -177,6 +178,11 @@ export type LocationSpec = _LocationSpec;
|
||||
* @deprecated import from `@backstage/plugin-catalog-node` instead
|
||||
*/
|
||||
export type AnalyzeOptions = _AnalyzeOptions;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-catalog-node` instead
|
||||
*/
|
||||
export type LocationAnalyzer = _LocationAnalyzer;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated import from `@backstage/plugin-catalog-node` instead
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export * from './ingestion';
|
||||
export * from './modules';
|
||||
export * from './processing';
|
||||
export * from './search';
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { LocationAnalyzer } from './types';
|
||||
import {
|
||||
AnalyzeLocationRequest,
|
||||
AnalyzeLocationResponse,
|
||||
} from '@backstage/plugin-catalog-common';
|
||||
import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-node';
|
||||
import {
|
||||
LocationAnalyzer,
|
||||
ScmLocationAnalyzer,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
|
||||
export class RepoLocationAnalyzer implements LocationAnalyzer {
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
EntitiesSearchFilter,
|
||||
EntityProvider,
|
||||
PlaceholderResolver,
|
||||
LocationAnalyzer,
|
||||
ScmLocationAnalyzer,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import {
|
||||
@@ -64,7 +65,6 @@ import {
|
||||
yamlPlaceholderResolver,
|
||||
} from '../modules/core/PlaceholderProcessor';
|
||||
import { defaultEntityDataParser } from '../modules/util/parse';
|
||||
import { LocationAnalyzer } from '../ingestion';
|
||||
import {
|
||||
CatalogProcessingEngine,
|
||||
createRandomProcessingInterval,
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
import { Entity, Validators } from '@backstage/catalog-model';
|
||||
import { CatalogBuilder, CatalogPermissionRuleInput } from './CatalogBuilder';
|
||||
import {
|
||||
CatalogAnalysisExtensionPoint,
|
||||
catalogAnalysisExtensionPoint,
|
||||
CatalogModelExtensionPoint,
|
||||
catalogModelExtensionPoint,
|
||||
@@ -33,11 +32,13 @@ import {
|
||||
CatalogProcessor,
|
||||
CatalogProcessorParser,
|
||||
EntityProvider,
|
||||
LocationAnalyzer,
|
||||
PlaceholderResolver,
|
||||
ScmLocationAnalyzer,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import { merge } from 'lodash';
|
||||
import { Permission } from '@backstage/plugin-permission-common';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
|
||||
class CatalogProcessingExtensionPointImpl
|
||||
implements CatalogProcessingExtensionPoint
|
||||
@@ -96,20 +97,6 @@ class CatalogProcessingExtensionPointImpl
|
||||
}
|
||||
}
|
||||
|
||||
class CatalogAnalysisExtensionPointImpl
|
||||
implements CatalogAnalysisExtensionPoint
|
||||
{
|
||||
#locationAnalyzers = new Array<ScmLocationAnalyzer>();
|
||||
|
||||
addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void {
|
||||
this.#locationAnalyzers.push(analyzer);
|
||||
}
|
||||
|
||||
get locationAnalyzers() {
|
||||
return this.#locationAnalyzers;
|
||||
}
|
||||
}
|
||||
|
||||
class CatalogPermissionExtensionPointImpl
|
||||
implements CatalogPermissionExtensionPoint
|
||||
{
|
||||
@@ -178,11 +165,29 @@ export const catalogPlugin = createBackendPlugin({
|
||||
processingExtensions,
|
||||
);
|
||||
|
||||
const analysisExtensions = new CatalogAnalysisExtensionPointImpl();
|
||||
env.registerExtensionPoint(
|
||||
catalogAnalysisExtensionPoint,
|
||||
analysisExtensions,
|
||||
);
|
||||
let locationAnalyzerFactory:
|
||||
| ((options: {
|
||||
scmLocationAnalyzers: ScmLocationAnalyzer[];
|
||||
}) => Promise<{ locationAnalyzer: LocationAnalyzer }>)
|
||||
| undefined = undefined;
|
||||
const scmLocationAnalyzers = new Array<ScmLocationAnalyzer>();
|
||||
env.registerExtensionPoint(catalogAnalysisExtensionPoint, {
|
||||
setLocationAnalyzer(analyzerOrFactory) {
|
||||
if (locationAnalyzerFactory) {
|
||||
throw new Error('LocationAnalyzer has already been set');
|
||||
}
|
||||
if (typeof analyzerOrFactory === 'function') {
|
||||
locationAnalyzerFactory = analyzerOrFactory;
|
||||
} else {
|
||||
locationAnalyzerFactory = async () => ({
|
||||
locationAnalyzer: analyzerOrFactory,
|
||||
});
|
||||
}
|
||||
},
|
||||
addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer) {
|
||||
scmLocationAnalyzers.push(analyzer);
|
||||
},
|
||||
});
|
||||
|
||||
const permissionExtensions = new CatalogPermissionExtensionPointImpl();
|
||||
env.registerExtensionPoint(
|
||||
@@ -246,7 +251,16 @@ export const catalogPlugin = createBackendPlugin({
|
||||
Object.entries(processingExtensions.placeholderResolvers).forEach(
|
||||
([key, resolver]) => builder.setPlaceholderResolver(key, resolver),
|
||||
);
|
||||
builder.addLocationAnalyzers(...analysisExtensions.locationAnalyzers);
|
||||
if (locationAnalyzerFactory) {
|
||||
const { locationAnalyzer } = await locationAnalyzerFactory({
|
||||
scmLocationAnalyzers,
|
||||
}).catch(e => {
|
||||
throw new ForwardedError('Failed to create LocationAnalyzer', e);
|
||||
});
|
||||
builder.setLocationAnalyzer(locationAnalyzer);
|
||||
} else {
|
||||
builder.addLocationAnalyzers(...scmLocationAnalyzers);
|
||||
}
|
||||
builder.addPermissions(...permissionExtensions.permissions);
|
||||
builder.addPermissionRules(...permissionExtensions.permissionRules);
|
||||
builder.setFieldFormatValidators(modelExtensions.fieldValidators);
|
||||
|
||||
@@ -42,7 +42,7 @@ import { decodeCursor, encodeCursor } from './util';
|
||||
import { wrapInOpenApiTestServer } from '@backstage/backend-openapi-utils';
|
||||
import { Server } from 'http';
|
||||
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
|
||||
import { LocationAnalyzer } from '../ingestion';
|
||||
import { LocationAnalyzer } from '@backstage/plugin-catalog-node';
|
||||
|
||||
describe('createRouter readonly disabled', () => {
|
||||
let entitiesCatalog: jest.Mocked<EntitiesCatalog>;
|
||||
|
||||
@@ -28,7 +28,6 @@ import express from 'express';
|
||||
import yn from 'yn';
|
||||
import { z } from 'zod';
|
||||
import { EntitiesCatalog } from '../catalog/types';
|
||||
import { LocationAnalyzer } from '../ingestion';
|
||||
import { CatalogProcessingOrchestrator } from '../processing/types';
|
||||
import { validateEntityEnvelope } from '../processing/util';
|
||||
import {
|
||||
@@ -55,6 +54,7 @@ import {
|
||||
HttpAuthService,
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { LocationAnalyzer } from '@backstage/plugin-catalog-node';
|
||||
|
||||
/**
|
||||
* Options used by {@link createRouter}.
|
||||
|
||||
@@ -10,6 +10,7 @@ import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-node';
|
||||
import { ExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
import { LocationAnalyzer } from '@backstage/plugin-catalog-node';
|
||||
import { Permission } from '@backstage/plugin-permission-common';
|
||||
import { PermissionRule } from '@backstage/plugin-permission-node';
|
||||
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
|
||||
@@ -20,8 +21,14 @@ import { Validators } from '@backstage/catalog-model';
|
||||
|
||||
// @alpha (undocumented)
|
||||
export interface CatalogAnalysisExtensionPoint {
|
||||
// (undocumented)
|
||||
addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void;
|
||||
addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer): void;
|
||||
setLocationAnalyzer(
|
||||
analyzerOrFactory:
|
||||
| LocationAnalyzer
|
||||
| ((options: { scmLocationAnalyzers: ScmLocationAnalyzer[] }) => Promise<{
|
||||
locationAnalyzer: LocationAnalyzer;
|
||||
}>),
|
||||
): void;
|
||||
}
|
||||
|
||||
// @alpha (undocumented)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common';
|
||||
import { AnalyzeLocationRequest } from '@backstage/plugin-catalog-common';
|
||||
import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common';
|
||||
import { CompoundEntityRef } from '@backstage/catalog-model';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
@@ -166,6 +168,13 @@ export type EntityRelationSpec = {
|
||||
target: CompoundEntityRef;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type LocationAnalyzer = {
|
||||
analyzeLocation(
|
||||
location: AnalyzeLocationRequest,
|
||||
): Promise<AnalyzeLocationResponse>;
|
||||
};
|
||||
|
||||
// @public @deprecated
|
||||
export type LocationSpec = LocationSpec_2;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
EntitiesSearchFilter,
|
||||
EntityProvider,
|
||||
PlaceholderResolver,
|
||||
LocationAnalyzer,
|
||||
ScmLocationAnalyzer,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import {
|
||||
@@ -79,7 +80,27 @@ export const catalogProcessingExtensionPoint =
|
||||
* @alpha
|
||||
*/
|
||||
export interface CatalogAnalysisExtensionPoint {
|
||||
addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void;
|
||||
/**
|
||||
* Replaces the entire location analyzer with a new one.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* By providing a factory function you can access all the SCM analyzers that
|
||||
* have been added through `addScmLocationAnalyzer`. If you provide a
|
||||
* `LocationAnalyzer` directly, the SCM analyzers will be ignored.
|
||||
*/
|
||||
setLocationAnalyzer(
|
||||
analyzerOrFactory:
|
||||
| LocationAnalyzer
|
||||
| ((options: {
|
||||
scmLocationAnalyzers: ScmLocationAnalyzer[];
|
||||
}) => Promise<{ locationAnalyzer: LocationAnalyzer }>),
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Adds an analyzer for a specific SCM type to the default location analyzer.
|
||||
*/
|
||||
addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,5 +21,6 @@ export type {
|
||||
PlaceholderResolverParams,
|
||||
PlaceholderResolverRead,
|
||||
PlaceholderResolverResolveUrl,
|
||||
LocationAnalyzer,
|
||||
ScmLocationAnalyzer,
|
||||
} from './types';
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common';
|
||||
import {
|
||||
AnalyzeLocationExistingEntity,
|
||||
AnalyzeLocationRequest,
|
||||
AnalyzeLocationResponse,
|
||||
} from '@backstage/plugin-catalog-common';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { CatalogProcessorEmit } from '../api';
|
||||
|
||||
@@ -52,6 +56,19 @@ export type PlaceholderResolver = (
|
||||
params: PlaceholderResolverParams,
|
||||
) => Promise<JsonValue>;
|
||||
|
||||
/** @public */
|
||||
export type LocationAnalyzer = {
|
||||
/**
|
||||
* Generates an entity configuration for given git repository. It's used for
|
||||
* importing new component to the backstage app.
|
||||
*
|
||||
* @param location - Git repository to analyze and generate config for.
|
||||
*/
|
||||
analyzeLocation(
|
||||
location: AnalyzeLocationRequest,
|
||||
): Promise<AnalyzeLocationResponse>;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type AnalyzeOptions = {
|
||||
url: string;
|
||||
|
||||
@@ -28,6 +28,7 @@ Here's how to get the DevTools Backend up and running:
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
permissions: env.permissions,
|
||||
discovery: env.discovery,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
@@ -102,6 +102,7 @@ export const NotificationsSidebarItem: (props?: {
|
||||
webNotificationsEnabled?: boolean;
|
||||
titleCounterEnabled?: boolean;
|
||||
snackbarEnabled?: boolean;
|
||||
snackbarAutoHideDuration?: number | null;
|
||||
className?: string;
|
||||
icon?: IconComponent;
|
||||
text?: string;
|
||||
|
||||
+5
@@ -83,6 +83,7 @@ export const NotificationsSidebarItem = (props?: {
|
||||
webNotificationsEnabled?: boolean;
|
||||
titleCounterEnabled?: boolean;
|
||||
snackbarEnabled?: boolean;
|
||||
snackbarAutoHideDuration?: number | null;
|
||||
className?: string;
|
||||
icon?: IconComponent;
|
||||
text?: string;
|
||||
@@ -93,6 +94,7 @@ export const NotificationsSidebarItem = (props?: {
|
||||
webNotificationsEnabled = false,
|
||||
titleCounterEnabled = true,
|
||||
snackbarEnabled = true,
|
||||
snackbarAutoHideDuration = 10000,
|
||||
icon = NotificationsIcon,
|
||||
text = 'Notifications',
|
||||
...restProps
|
||||
@@ -100,6 +102,7 @@ export const NotificationsSidebarItem = (props?: {
|
||||
webNotificationsEnabled: false,
|
||||
titleCounterEnabled: true,
|
||||
snackbarEnabled: true,
|
||||
snackbarAutoHideDuration: 10000,
|
||||
};
|
||||
|
||||
const { loading, error, value, retry } = useNotificationsApi(api =>
|
||||
@@ -196,6 +199,7 @@ export const NotificationsSidebarItem = (props?: {
|
||||
variant: notification.payload.severity,
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
|
||||
action,
|
||||
autoHideDuration: snackbarAutoHideDuration,
|
||||
} as OptionsWithExtraProps<VariantType>);
|
||||
}
|
||||
})
|
||||
@@ -216,6 +220,7 @@ export const NotificationsSidebarItem = (props?: {
|
||||
sendWebNotification,
|
||||
webNotificationsEnabled,
|
||||
snackbarEnabled,
|
||||
snackbarAutoHideDuration,
|
||||
notificationsApi,
|
||||
alertApi,
|
||||
getSnackbarProperties,
|
||||
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2021 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 { createGitlabProjectVariableAction } from './createGitlabProjectVariableAction';
|
||||
import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import yaml from 'yaml';
|
||||
import { examples } from './createGitlabProjectVariableAction.examples';
|
||||
|
||||
const mockGitlabClient = {
|
||||
ProjectVariables: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
};
|
||||
jest.mock('@gitbeaker/node', () => ({
|
||||
Gitlab: class {
|
||||
constructor() {
|
||||
return mockGitlabClient;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
describe('gitlab:projectVariableAction: create examples', () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
token: 'tokenlols',
|
||||
apiBaseUrl: 'https://api.gitlab.com',
|
||||
},
|
||||
{
|
||||
host: 'hosted.gitlab.com',
|
||||
apiBaseUrl: 'https://api.hosted.gitlab.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const action = createGitlabProjectVariableAction({ integrations });
|
||||
const mockContext = createMockActionContext({
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
projectId: '123',
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
variableType: 'env_var',
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it(`Should ${examples[0].description}`, async () => {
|
||||
mockGitlabClient.ProjectVariables.create.mockResolvedValue({
|
||||
token: 'TOKEN',
|
||||
});
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: yaml.parse(examples[0].example).steps[0].input,
|
||||
});
|
||||
|
||||
expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith(
|
||||
'123',
|
||||
{
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
variable_type: 'env_var',
|
||||
environment_scope: '*',
|
||||
masked: false,
|
||||
protected: false,
|
||||
raw: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
it(`Should ${examples[1].description}`, async () => {
|
||||
mockGitlabClient.ProjectVariables.create.mockResolvedValue({
|
||||
token: 'TOKEN',
|
||||
});
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: yaml.parse(examples[1].example).steps[0].input,
|
||||
});
|
||||
|
||||
expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith(
|
||||
'123',
|
||||
{
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my-file-content',
|
||||
protected: false,
|
||||
masked: false,
|
||||
raw: false,
|
||||
environment_scope: '*',
|
||||
variable_type: 'file',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it(`Should ${examples[2].description}`, async () => {
|
||||
mockGitlabClient.ProjectVariables.create.mockResolvedValue({
|
||||
token: 'TOKEN',
|
||||
});
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: yaml.parse(examples[2].example).steps[0].input,
|
||||
});
|
||||
|
||||
expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith(
|
||||
'456',
|
||||
{
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
masked: false,
|
||||
raw: false,
|
||||
environment_scope: '*',
|
||||
variable_type: 'env_var',
|
||||
protected: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it(`Should ${examples[3].description}`, async () => {
|
||||
mockGitlabClient.ProjectVariables.create.mockResolvedValue({
|
||||
token: 'TOKEN',
|
||||
});
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: yaml.parse(examples[3].example).steps[0].input,
|
||||
});
|
||||
|
||||
expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith(
|
||||
'789',
|
||||
{
|
||||
key: 'DB_PASSWORD',
|
||||
value: 'password123',
|
||||
protected: false,
|
||||
raw: false,
|
||||
environment_scope: '*',
|
||||
variable_type: 'env_var',
|
||||
masked: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it(`Should ${examples[4].description}`, async () => {
|
||||
mockGitlabClient.ProjectVariables.create.mockResolvedValue({
|
||||
token: 'TOKEN',
|
||||
});
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: yaml.parse(examples[4].example).steps[0].input,
|
||||
});
|
||||
|
||||
expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith(
|
||||
'123',
|
||||
{
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
protected: false,
|
||||
environment_scope: '*',
|
||||
variable_type: 'env_var',
|
||||
masked: false,
|
||||
raw: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it(`Should ${examples[5].description}`, async () => {
|
||||
mockGitlabClient.ProjectVariables.create.mockResolvedValue({
|
||||
token: 'TOKEN',
|
||||
});
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: yaml.parse(examples[5].example).steps[0].input,
|
||||
});
|
||||
|
||||
expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith(
|
||||
'123',
|
||||
{
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
protected: false,
|
||||
variable_type: 'env_var',
|
||||
masked: false,
|
||||
raw: false,
|
||||
environment_scope: 'production',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it(`Should ${examples[6].description}`, async () => {
|
||||
mockGitlabClient.ProjectVariables.create.mockResolvedValue({
|
||||
token: 'TOKEN',
|
||||
});
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: yaml.parse(examples[6].example).steps[0].input,
|
||||
});
|
||||
|
||||
expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith(
|
||||
'123',
|
||||
{
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
protected: false,
|
||||
variable_type: 'env_var',
|
||||
masked: false,
|
||||
raw: false,
|
||||
environment_scope: '*',
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2023 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 { TemplateExample } from '@backstage/plugin-scaffolder-node';
|
||||
import yaml from 'yaml';
|
||||
|
||||
export const examples: TemplateExample[] = [
|
||||
{
|
||||
description: 'Creating a GitLab project variable of type env_var',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'createVariable',
|
||||
action: 'gitlab:createGitlabProjectVariableAction',
|
||||
name: 'Create GitLab Project Variable',
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
projectId: '123',
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
variableType: 'env_var',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Creating a GitLab project variable of type file',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'createVariable',
|
||||
action: 'gitlab:createGitlabProjectVariableAction',
|
||||
name: 'Create GitLab Project Variable',
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
projectId: '123',
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my-file-content',
|
||||
variableType: 'file',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Create a GitLab project variable that is protected.',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'createVariable',
|
||||
action: 'gitlab:createGitlabProjectVariableAction',
|
||||
name: 'Create GitLab Project Variable',
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
projectId: '456',
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
variableType: 'env_var',
|
||||
variableProtected: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Create a GitLab project variable with masked flag as true',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'createVariable',
|
||||
action: 'gitlab:createGitlabProjectVariableAction',
|
||||
name: 'Create GitLab Project Variable',
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
projectId: '789',
|
||||
key: 'DB_PASSWORD',
|
||||
value: 'password123',
|
||||
variableType: 'env_var',
|
||||
masked: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Create a GitLab project variable that is expandable.',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'createVariable',
|
||||
action: 'gitlab:projectVariable:create',
|
||||
name: 'Create GitLab Project Variable',
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
projectId: '123',
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
variableType: 'env_var',
|
||||
raw: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Create a GitLab project variable with a specific environment scope.',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'createVariable',
|
||||
action: 'gitlab:projectVariable:create',
|
||||
name: 'Create GitLab Project Variable',
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
projectId: '123',
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
variableType: 'env_var',
|
||||
environmentScope: 'production',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Create a GitLab project variable with a wildcard environment scope.',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'createVariable',
|
||||
action: 'gitlab:projectVariable:create',
|
||||
name: 'Create GitLab Project Variable',
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
projectId: '123',
|
||||
key: 'MY_VARIABLE',
|
||||
value: 'my_value',
|
||||
variableType: 'env_var',
|
||||
environmentScope: '*',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
+2
-1
@@ -20,6 +20,7 @@ import { Gitlab } from '@gitbeaker/node';
|
||||
import { getToken } from '../util';
|
||||
import commonGitlabConfig from '../commonGitlabConfig';
|
||||
import { z } from 'zod';
|
||||
import { examples } from './createGitlabProjectVariableAction.examples';
|
||||
|
||||
/**
|
||||
* Creates a `gitlab:projectVariable:create` Scaffolder action.
|
||||
@@ -33,6 +34,7 @@ export const createGitlabProjectVariableAction = (options: {
|
||||
const { integrations } = options;
|
||||
return createTemplateAction({
|
||||
id: 'gitlab:projectVariable:create',
|
||||
examples,
|
||||
schema: {
|
||||
input: commonGitlabConfig.merge(
|
||||
z.object({
|
||||
@@ -85,7 +87,6 @@ export const createGitlabProjectVariableAction = (options: {
|
||||
host: integrationConfig.config.baseUrl,
|
||||
token: token,
|
||||
});
|
||||
|
||||
await api.ProjectVariables.create(projectId, {
|
||||
key: key,
|
||||
value: value,
|
||||
|
||||
@@ -190,7 +190,8 @@ export const SearchModal = (props: SearchModalProps) => {
|
||||
paperFullWidth: classes.paperFullWidth,
|
||||
}}
|
||||
onClose={toggleModal}
|
||||
aria-labelledby="search-modal-title"
|
||||
aria-label="Search Modal"
|
||||
aria-modal="true"
|
||||
fullWidth
|
||||
maxWidth="lg"
|
||||
open={open}
|
||||
|
||||
@@ -7817,7 +7817,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@changesets/assemble-release-plan@npm:^6.0.0":
|
||||
"@changesets/assemble-release-plan@npm:6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "@changesets/assemble-release-plan@npm:6.0.0"
|
||||
dependencies:
|
||||
@@ -7831,6 +7831,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@changesets/assemble-release-plan@patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch::locator=root%40workspace%3A.":
|
||||
version: 6.0.0
|
||||
resolution: "@changesets/assemble-release-plan@patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch::version=6.0.0&hash=43c5e4&locator=root%40workspace%3A."
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.20.1
|
||||
"@changesets/errors": ^0.2.0
|
||||
"@changesets/get-dependents-graph": ^2.0.0
|
||||
"@changesets/types": ^6.0.0
|
||||
"@manypkg/get-packages": ^1.1.3
|
||||
semver: ^7.5.3
|
||||
checksum: b7a68e28d03379bdc2a1d7171963990e1b88d0e5efca5f5ed490c0f583d66d0ccbd4c04ebf4e5cd1c38e1346ff0e5c4c16e1b4973cdd97fabdbad0c6996fe016
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@changesets/changelog-git@npm:^0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "@changesets/changelog-git@npm:0.2.0"
|
||||
@@ -12240,24 +12254,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@puppeteer/browsers@npm:2.2.1":
|
||||
version: 2.2.1
|
||||
resolution: "@puppeteer/browsers@npm:2.2.1"
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
extract-zip: 2.0.1
|
||||
progress: 2.0.3
|
||||
proxy-agent: 6.4.0
|
||||
semver: 7.6.0
|
||||
tar-fs: 3.0.5
|
||||
unbzip2-stream: 1.4.3
|
||||
yargs: 17.7.2
|
||||
bin:
|
||||
browsers: lib/cjs/main-cli.js
|
||||
checksum: c2ec8bac9978ae6279d67442a3a81c517db1e172bd4603d1983eb48de12b088d8b565b1817abe21ba6df76be9a68e3fc543d4c7111c964a93f3ac9b14f7c76e5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@radix-ui/primitive@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@radix-ui/primitive@npm:1.0.1"
|
||||
@@ -16481,15 +16477,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/puppeteer@npm:^7.0.0":
|
||||
version: 7.0.4
|
||||
resolution: "@types/puppeteer@npm:7.0.4"
|
||||
dependencies:
|
||||
puppeteer: "*"
|
||||
checksum: c84a44b054454c13935a9cf0f8983166e238532397af4f321c918d89b43a91f854460e3d0dda122f72c258b444dbcdc04ada950e35adc1938ff3b7831c6fd7a4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/qs@npm:*":
|
||||
version: 6.9.6
|
||||
resolution: "@types/qs@npm:6.9.6"
|
||||
@@ -16878,12 +16865,12 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@types/tar@npm:^6.1.1":
|
||||
version: 6.1.12
|
||||
resolution: "@types/tar@npm:6.1.12"
|
||||
version: 6.1.13
|
||||
resolution: "@types/tar@npm:6.1.13"
|
||||
dependencies:
|
||||
"@types/node": "*"
|
||||
minipass: ^4.0.0
|
||||
checksum: b1cbae1894cc943e3a86f88613853986f97f552c6bec34ee990a47fe5905871d1552397ff440108233d75d05653be2fbc356e62beb0b93a45b927fb88060b438
|
||||
checksum: bb3910936a6b37f093e38b73a52b0544fd73079685f5ea72e5c049fddc3770e58d80cf6d714425853f0746290221852c1a7ca89ffdb9614f3b2a858a3bf5436a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -17067,7 +17054,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/yauzl@npm:^2.10.0, @types/yauzl@npm:^2.9.1":
|
||||
"@types/yauzl@npm:^2.10.0":
|
||||
version: 2.10.3
|
||||
resolution: "@types/yauzl@npm:2.10.3"
|
||||
dependencies:
|
||||
@@ -19641,7 +19628,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"buffer@npm:^5.2.1, buffer@npm:^5.5.0, buffer@npm:^5.7.1":
|
||||
"buffer@npm:^5.5.0, buffer@npm:^5.7.1":
|
||||
version: 5.7.1
|
||||
resolution: "buffer@npm:5.7.1"
|
||||
dependencies:
|
||||
@@ -20131,19 +20118,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chromium-bidi@npm:0.5.17":
|
||||
version: 0.5.17
|
||||
resolution: "chromium-bidi@npm:0.5.17"
|
||||
dependencies:
|
||||
mitt: 3.0.1
|
||||
urlpattern-polyfill: 10.0.0
|
||||
zod: 3.22.4
|
||||
peerDependencies:
|
||||
devtools-protocol: "*"
|
||||
checksum: 522da996ed5abfb47707583cc24785f9aa05d87bd968dbd520f245cf8972fa3ec102f8d1d72fa07558daa70495d8c6f2bf364d8599eb60b77504e528601d8a30
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ci-info@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "ci-info@npm:2.0.0"
|
||||
@@ -21152,23 +21126,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cosmiconfig@npm:9.0.0":
|
||||
version: 9.0.0
|
||||
resolution: "cosmiconfig@npm:9.0.0"
|
||||
dependencies:
|
||||
env-paths: ^2.2.1
|
||||
import-fresh: ^3.3.0
|
||||
js-yaml: ^4.1.0
|
||||
parse-json: ^5.2.0
|
||||
peerDependencies:
|
||||
typescript: ">=4.9.5"
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: a30c424b53d442ea0bdd24cb1b3d0d8687c8dda4a17ab6afcdc439f8964438801619cdb66e8e79f63b9caa3e6586b60d8bab9ce203e72df6c5e80179b971fe8f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cosmiconfig@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "cosmiconfig@npm:6.0.0"
|
||||
@@ -22409,13 +22366,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"devtools-protocol@npm:0.0.1262051":
|
||||
version: 0.0.1262051
|
||||
resolution: "devtools-protocol@npm:0.0.1262051"
|
||||
checksum: beaad00059964a661ab056d5e993492742c612c0370c6f08acd91490181c4d4ecf57d316eedb5a37fb6bb59321901d09ce50762f79ea09a50751d86f601b8f8e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dezalgo@npm:^1.0.0, dezalgo@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "dezalgo@npm:1.0.4"
|
||||
@@ -22825,7 +22775,6 @@ __metadata:
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@types/fs-extra": ^11.0.0
|
||||
"@types/node": ^18.17.8
|
||||
"@types/puppeteer": ^7.0.0
|
||||
chalk: ^4.0.0
|
||||
commander: ^12.0.0
|
||||
cross-fetch: ^4.0.0
|
||||
@@ -22908,13 +22857,13 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"ejs@npm:^3.1.6":
|
||||
version: 3.1.9
|
||||
resolution: "ejs@npm:3.1.9"
|
||||
version: 3.1.10
|
||||
resolution: "ejs@npm:3.1.10"
|
||||
dependencies:
|
||||
jake: ^10.8.5
|
||||
bin:
|
||||
ejs: bin/cli.js
|
||||
checksum: af6f10eb815885ff8a8cfacc42c6b6cf87daf97a4884f87a30e0c3271fedd85d76a3a297d9c33a70e735b97ee632887f85e32854b9cdd3a2d97edf931519a35f
|
||||
checksum: ce90637e9c7538663ae023b8a7a380b2ef7cc4096de70be85abf5a3b9641912dde65353211d05e24d56b1f242d71185c6d00e02cb8860701d571786d92c71f05
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -24575,23 +24524,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"extract-zip@npm:2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "extract-zip@npm:2.0.1"
|
||||
dependencies:
|
||||
"@types/yauzl": ^2.9.1
|
||||
debug: ^4.1.1
|
||||
get-stream: ^5.1.0
|
||||
yauzl: ^2.10.0
|
||||
dependenciesMeta:
|
||||
"@types/yauzl":
|
||||
optional: true
|
||||
bin:
|
||||
extract-zip: cli.js
|
||||
checksum: 8cbda9debdd6d6980819cc69734d874ddd71051c9fe5bde1ef307ebcedfe949ba57b004894b585f758b7c9eeeea0e3d87f2dda89b7d25320459c2c9643ebb635
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"extsprintf@npm:1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "extsprintf@npm:1.3.0"
|
||||
@@ -24824,15 +24756,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fd-slicer@npm:~1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "fd-slicer@npm:1.1.0"
|
||||
dependencies:
|
||||
pend: ~1.2.0
|
||||
checksum: c8585fd5713f4476eb8261150900d2cb7f6ff2d87f8feb306ccc8a1122efd152f1783bdb2b8dc891395744583436bfd8081d8e63ece0ec8687eeefea394d4ff2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fecha@npm:^4.2.0":
|
||||
version: 4.2.0
|
||||
resolution: "fecha@npm:4.2.0"
|
||||
@@ -26699,7 +26622,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1":
|
||||
"http-proxy-agent@npm:^7.0.0":
|
||||
version: 7.0.2
|
||||
resolution: "http-proxy-agent@npm:7.0.2"
|
||||
dependencies:
|
||||
@@ -26797,7 +26720,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"https-proxy-agent@npm:7.0.4, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.2, https-proxy-agent@npm:^7.0.3":
|
||||
"https-proxy-agent@npm:7.0.4, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.2":
|
||||
version: 7.0.4
|
||||
resolution: "https-proxy-agent@npm:7.0.4"
|
||||
dependencies:
|
||||
@@ -31776,13 +31699,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mitt@npm:3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "mitt@npm:3.0.1"
|
||||
checksum: b55a489ac9c2949ab166b7f060601d3b6d893a852515ae9eca4e11df01c013876df777ea109317622b5c1c60e8aae252558e33c8c94e14124db38f64a39614b1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mixme@npm:^0.5.1":
|
||||
version: 0.5.4
|
||||
resolution: "mixme@npm:0.5.4"
|
||||
@@ -33518,7 +33434,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pac-proxy-agent@npm:^7.0.0, pac-proxy-agent@npm:^7.0.1":
|
||||
"pac-proxy-agent@npm:^7.0.0":
|
||||
version: 7.0.1
|
||||
resolution: "pac-proxy-agent@npm:7.0.1"
|
||||
dependencies:
|
||||
@@ -35161,13 +35077,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"progress@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "progress@npm:2.0.3"
|
||||
checksum: f67403fe7b34912148d9252cb7481266a354bd99ce82c835f79070643bb3c6583d10dbcfda4d41e04bbc1d8437e9af0fb1e1f2135727878f5308682a579429b7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prom-client@npm:^15.0.0":
|
||||
version: 15.1.2
|
||||
resolution: "prom-client@npm:15.1.2"
|
||||
@@ -35355,22 +35264,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proxy-agent@npm:6.4.0":
|
||||
version: 6.4.0
|
||||
resolution: "proxy-agent@npm:6.4.0"
|
||||
dependencies:
|
||||
agent-base: ^7.0.2
|
||||
debug: ^4.3.4
|
||||
http-proxy-agent: ^7.0.1
|
||||
https-proxy-agent: ^7.0.3
|
||||
lru-cache: ^7.14.1
|
||||
pac-proxy-agent: ^7.0.1
|
||||
proxy-from-env: ^1.1.0
|
||||
socks-proxy-agent: ^8.0.2
|
||||
checksum: 4d3794ad5e07486298902f0a7f250d0f869fa0e92d790767ca3f793a81374ce0ab6c605f8ab8e791c4d754da96656b48d1c24cb7094bfd310a15867e4a0841d7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proxy-from-env@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "proxy-from-env@npm:1.1.0"
|
||||
@@ -35453,33 +35346,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"puppeteer-core@npm:22.6.4":
|
||||
version: 22.6.4
|
||||
resolution: "puppeteer-core@npm:22.6.4"
|
||||
dependencies:
|
||||
"@puppeteer/browsers": 2.2.1
|
||||
chromium-bidi: 0.5.17
|
||||
debug: 4.3.4
|
||||
devtools-protocol: 0.0.1262051
|
||||
ws: 8.16.0
|
||||
checksum: 76d4328e7d2a788a7a2dc3a8b19571f8eda842ff990da2fe773c3e5c01236a03856b67d9a2a6ae976f8fa74ab38813e2c7e7c4b15b9715965b2156e9ca1f9a78
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"puppeteer@npm:*":
|
||||
version: 22.6.4
|
||||
resolution: "puppeteer@npm:22.6.4"
|
||||
dependencies:
|
||||
"@puppeteer/browsers": 2.2.1
|
||||
cosmiconfig: 9.0.0
|
||||
devtools-protocol: 0.0.1262051
|
||||
puppeteer-core: 22.6.4
|
||||
bin:
|
||||
puppeteer: lib/esm/puppeteer/node/cli.js
|
||||
checksum: 7dcbda7fc3b999f3ee7808d1c67da41be0aeabd66b0831711a46a897a2681d117f044263e57aeb7d0a096c420bb9f5871c5c2b21c38e40d323fa675e1de322c9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pure-color@npm:^1.2.0":
|
||||
version: 1.3.0
|
||||
resolution: "pure-color@npm:1.3.0"
|
||||
@@ -37830,7 +37696,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver@npm:7.6.0, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.4.0, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0":
|
||||
"semver@npm:^6.0.0, semver@npm:^6.1.0, semver@npm:^6.2.0, semver@npm:^6.3.0, semver@npm:^6.3.1":
|
||||
version: 6.3.1
|
||||
resolution: "semver@npm:6.3.1"
|
||||
bin:
|
||||
semver: bin/semver.js
|
||||
checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.4.0, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0":
|
||||
version: 7.6.0
|
||||
resolution: "semver@npm:7.6.0"
|
||||
dependencies:
|
||||
@@ -37841,15 +37716,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver@npm:^6.0.0, semver@npm:^6.1.0, semver@npm:^6.2.0, semver@npm:^6.3.0, semver@npm:^6.3.1":
|
||||
version: 6.3.1
|
||||
resolution: "semver@npm:6.3.1"
|
||||
bin:
|
||||
semver: bin/semver.js
|
||||
checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"send@npm:0.18.0":
|
||||
version: 0.18.0
|
||||
resolution: "send@npm:0.18.0"
|
||||
@@ -39590,7 +39456,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar-fs@npm:3.0.5, tar-fs@npm:^3.0.5":
|
||||
"tar-fs@npm:^2.0.0":
|
||||
version: 2.1.1
|
||||
resolution: "tar-fs@npm:2.1.1"
|
||||
dependencies:
|
||||
chownr: ^1.1.1
|
||||
mkdirp-classic: ^0.5.2
|
||||
pump: ^3.0.0
|
||||
tar-stream: ^2.1.4
|
||||
checksum: f5b9a70059f5b2969e65f037b4e4da2daf0fa762d3d232ffd96e819e3f94665dbbbe62f76f084f1acb4dbdcce16c6e4dac08d12ffc6d24b8d76720f4d9cf032d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar-fs@npm:^3.0.5":
|
||||
version: 3.0.5
|
||||
resolution: "tar-fs@npm:3.0.5"
|
||||
dependencies:
|
||||
@@ -39607,18 +39485,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar-fs@npm:^2.0.0":
|
||||
version: 2.1.1
|
||||
resolution: "tar-fs@npm:2.1.1"
|
||||
dependencies:
|
||||
chownr: ^1.1.1
|
||||
mkdirp-classic: ^0.5.2
|
||||
pump: ^3.0.0
|
||||
tar-stream: ^2.1.4
|
||||
checksum: f5b9a70059f5b2969e65f037b4e4da2daf0fa762d3d232ffd96e819e3f94665dbbbe62f76f084f1acb4dbdcce16c6e4dac08d12ffc6d24b8d76720f4d9cf032d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar-fs@npm:~2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "tar-fs@npm:2.0.1"
|
||||
@@ -39886,7 +39752,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"through@npm:2, through@npm:^2.3.6, through@npm:^2.3.8":
|
||||
"through@npm:2, through@npm:^2.3.6":
|
||||
version: 2.3.8
|
||||
resolution: "through@npm:2.3.8"
|
||||
checksum: a38c3e059853c494af95d50c072b83f8b676a9ba2818dcc5b108ef252230735c54e0185437618596c790bbba8fcdaef5b290405981ffa09dce67b1f1bf190cbd
|
||||
@@ -40743,16 +40609,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unbzip2-stream@npm:1.4.3":
|
||||
version: 1.4.3
|
||||
resolution: "unbzip2-stream@npm:1.4.3"
|
||||
dependencies:
|
||||
buffer: ^5.2.1
|
||||
through: ^2.3.8
|
||||
checksum: 0e67c4a91f4fa0fc7b4045f8b914d3498c2fc2e8c39c359977708ec85ac6d6029840e97f508675fdbdf21fcb8d276ca502043406f3682b70f075e69aae626d1d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undefsafe@npm:^2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "undefsafe@npm:2.0.5"
|
||||
@@ -41150,13 +41006,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"urlpattern-polyfill@npm:10.0.0":
|
||||
version: 10.0.0
|
||||
resolution: "urlpattern-polyfill@npm:10.0.0"
|
||||
checksum: 61d890f151ea4ecf34a3dcab32c65ad1f3cda857c9d154af198260c6e5b2ad96d024593409baaa6d4428dd1ab206c14799bf37fe011117ac93a6a44913ac5aa4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"urlpattern-polyfill@npm:^9.0.0":
|
||||
version: 9.0.0
|
||||
resolution: "urlpattern-polyfill@npm:9.0.0"
|
||||
@@ -42275,7 +42124,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:*, ws@npm:8.16.0, ws@npm:^8.11.0, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.14.2, ws@npm:^8.16.0, ws@npm:^8.8.0":
|
||||
"ws@npm:*, ws@npm:^8.11.0, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.14.2, ws@npm:^8.16.0, ws@npm:^8.8.0":
|
||||
version: 8.16.0
|
||||
resolution: "ws@npm:8.16.0"
|
||||
peerDependencies:
|
||||
@@ -42566,21 +42415,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs@npm:17.7.2, yargs@npm:^17.1.1, yargs@npm:^17.3.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2":
|
||||
version: 17.7.2
|
||||
resolution: "yargs@npm:17.7.2"
|
||||
dependencies:
|
||||
cliui: ^8.0.1
|
||||
escalade: ^3.1.1
|
||||
get-caller-file: ^2.0.5
|
||||
require-directory: ^2.1.1
|
||||
string-width: ^4.2.3
|
||||
y18n: ^5.0.5
|
||||
yargs-parser: ^21.1.1
|
||||
checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs@npm:^15.1.0":
|
||||
version: 15.4.1
|
||||
resolution: "yargs@npm:15.4.1"
|
||||
@@ -42615,13 +42449,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yauzl@npm:^2.10.0":
|
||||
version: 2.10.0
|
||||
resolution: "yauzl@npm:2.10.0"
|
||||
"yargs@npm:^17.1.1, yargs@npm:^17.3.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2":
|
||||
version: 17.7.2
|
||||
resolution: "yargs@npm:17.7.2"
|
||||
dependencies:
|
||||
buffer-crc32: ~0.2.3
|
||||
fd-slicer: ~1.1.0
|
||||
checksum: 7f21fe0bbad6e2cb130044a5d1d0d5a0e5bf3d8d4f8c4e6ee12163ce798fee3de7388d22a7a0907f563ac5f9d40f8699a223d3d5c1718da90b0156da6904022b
|
||||
cliui: ^8.0.1
|
||||
escalade: ^3.1.1
|
||||
get-caller-file: ^2.0.5
|
||||
require-directory: ^2.1.1
|
||||
string-width: ^4.2.3
|
||||
y18n: ^5.0.5
|
||||
yargs-parser: ^21.1.1
|
||||
checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -42796,7 +42635,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:3.22.4, zod@npm:^3.22.4":
|
||||
"zod@npm:^3.22.4":
|
||||
version: 3.22.4
|
||||
resolution: "zod@npm:3.22.4"
|
||||
checksum: 80bfd7f8039b24fddeb0718a2ec7c02aa9856e4838d6aa4864335a047b6b37a3273b191ef335bf0b2002e5c514ef261ffcda5a589fb084a48c336ffc4cdbab7f
|
||||
|
||||
Reference in New Issue
Block a user