Merge branch 'master' into feat/api-auth-client
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Pass on plugin database management instance that is now required by the scaffolder plugin.
|
||||
|
||||
To apply this change to an existing application, add the following to `src/plugins/scaffolder.ts`:
|
||||
|
||||
```diff
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
+ database,
|
||||
}: PluginEnvironment) {
|
||||
|
||||
// ...omitted...
|
||||
|
||||
return await createRouter({
|
||||
preparers,
|
||||
templaters,
|
||||
publishers,
|
||||
logger,
|
||||
config,
|
||||
dockerClient,
|
||||
entityClient,
|
||||
+ database,
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-import': patch
|
||||
---
|
||||
|
||||
Export _api_ (Client, API, ref) from the catalog import plugin.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin as `scaffolderPlugin`. The template list page (`/create`) is exported as the `TemplateIndexPage` extension, and the templating page itself is exported as `TemplatePage`.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@backstage/techdocs-common': patch
|
||||
'@backstage/plugin-techdocs-backend': patch
|
||||
---
|
||||
|
||||
1. Added option to use Azure Blob Storage as a choice to store the static generated files for TechDocs.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-cost-insights': patch
|
||||
---
|
||||
|
||||
Fix snooze quarter option
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-cost-insights': minor
|
||||
---
|
||||
|
||||
add alert hooks
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-circleci': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `circleCIPlugin`, the entity page content as `EntityCircleCIContent`, and entity conditional as `isCircleCIAvailable`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Remove techdocs.requestUrl and techdocs.storageUrl from app-config.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Update `create-plugin` template to use the new composability API, by switching to exporting a single routable extension component.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search': minor
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `searchPlugin`, and page as `SearchPage`. Due to the old router component also being called `SearchPage`, this is a breaking change. The old page component is now exported as `Router`, which can be used to maintain the old behavior.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-graphiql': patch
|
||||
---
|
||||
|
||||
Finalized composability API migration, now exporting the plugin as `graphiqlPlugin`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Add className to the SidebarItem
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
use child logger, if provided, to log single location refresh
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Update `WarningPanel` component to use accordion-style expansion
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-app-backend': patch
|
||||
---
|
||||
|
||||
Added a new `disableConfigInjection` option, which can be used to disable the configuration injection in environments where it can't be used.
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-api-docs': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Display owner and system as entity page links in the tables of the `api-docs`
|
||||
plugin.
|
||||
|
||||
Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to
|
||||
`@backstage/plugin-catalog-react` and export it from there to use it by other
|
||||
plugins.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/theme': patch
|
||||
---
|
||||
|
||||
Updates warning text color to align to updated `WarningPanel` styling
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-import': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `catalogImportPlugin`, and the page as `CatalogImportPage`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-lighthouse': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `lighthousePlugin`, the top-level page as `LighthousePage`, the entity card as `EntityLastLighthouseAuditCard`, and the entity content as `EntityLighthouseContent`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Don't respond to a request twice if an entity has not been found.
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Updated the `rootLogger` in `@backstage/backend-common` to support custom logging options. This is useful when you want to make some changes without re-implementing the entire logger and calling `setRootLogger` or `logger.configure`. For example you can add additional `defaultMeta` tags to each log entry. The following changes are included:
|
||||
|
||||
- Added `createRootLogger` which accepts winston `LoggerOptions`. These options allow overriding the default keys.
|
||||
|
||||
Example Usage:
|
||||
|
||||
```ts
|
||||
// Create the logger
|
||||
const logger = createRootLogger({
|
||||
defaultMeta: { appName: 'backstage', appEnv: 'prod' },
|
||||
});
|
||||
|
||||
// Add a custom logger transport
|
||||
logger.add(new MyCustomTransport());
|
||||
|
||||
const config = await loadBackendConfig({
|
||||
argv: process.argv,
|
||||
logger: getRootLogger(), // already set to new logger instance
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-cloudbuild': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `cloudbuildPlugin`, the entity content as `EntityCloudbuildContent`, the entity conditional as `isCloudbuildAvailable`, and entity cards as `EntityLatestCloudbuildRunCard` and `EntityLatestCloudbuildsForBranchCard`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-user-settings': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin as `userSettingsPlugin` and the page as `UserSettingsPage`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-register-component': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `registerComponentPlugin`, and page as `RegisterComponentPage`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/catalog-model': patch
|
||||
---
|
||||
|
||||
Adds a new optional `links` metadata field to the Entity class within the `catalog-model` package (as discussed in [[RFC] Entity Links](https://github.com/backstage/backstage/issues/3787)). This PR adds support for the entity links only. Follow up PR's will introduce the UI component to display them.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'@backstage/techdocs-common': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
'@backstage/plugin-techdocs-backend': patch
|
||||
---
|
||||
|
||||
`techdocs.requestUrl` and `techdocs.storageUrl` are now optional configs and the discovery API will be used to get the URL where techdocs plugin is hosted.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/plugin-api-docs': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-lighthouse': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Improve display of error messages
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Add `children` option to `addPage`, which will be rendered as the children of the `Route`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
add indices on columns referring locations(id)
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
Introduced `v2` Scaffolder REST API, which uses an implementation that is database backed, making the scaffolder instances stateless. The `createRouter` function now requires a `PluginDatabaseManager` instance to be passed in, commonly available as `database` in the plugin environment in the backend.
|
||||
|
||||
This API should be considered unstable until used by the scaffolder frontend.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Attempt to fix windows test errors in master
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/catalog-model': patch
|
||||
---
|
||||
|
||||
Replace `yup` with `ajv`, for validation of catalog entities.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Fixed module resolution of external libraries during backend development. Modules used to be resolved relative to the backend entrypoint, but are now resolved relative to each individual module.
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
'@backstage/core': minor
|
||||
---
|
||||
|
||||
Closes #3556
|
||||
The scroll bar of collapsed sidebar is now hidden without full screen.
|
||||
|
||||

|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `orgPlugin`, and the entity cards as `EntityGroupProfileCard`, `EntityMembersListCard`, `EntityOwnershipCard`, and `EntityUserProfileCard`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
TechDocs: Add comments about migrating away from basic setup in app-config.yaml
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/config-loader': patch
|
||||
---
|
||||
|
||||
Bump `config-loader` to `ajv` 7, to enable v7 feature use elsewhere
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/dev-utils': patch
|
||||
---
|
||||
|
||||
Added `path` option to `addPage` that can be used to set a specific path for the page rather than a generated one. Also omit sidebar item altogether if `title` option is not set.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Properly forward errors that occur when looking up GitLab project IDs.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/config-loader': patch
|
||||
---
|
||||
|
||||
Each piece of the configuration schema is now validated upfront, in order to produce more informative errors.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-app-backend': patch
|
||||
---
|
||||
|
||||
Failures to load the frontend configuration schema now throws an error that includes more context and instructions for how to fix the issue.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `techdocsPlugin`, the top-level page as `TechdocsPage`, and the entity content as `EntityTechdocsContent`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Add a `prop` union for `SignInPage` that allows it to be used for just a single provider, with inline errors, and optionally with automatic sign-in.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Set explicit content-type in error handler responses.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Slight refactoring in support of a future search implementation in `UrlReader`. Mostly moving code around.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Add check for outdated/duplicate packages to yarn start
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-pagerduty': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `pagerDutyPlugin`, entity card as `EntityPagerDutyCard`, and entity conditional as `isPagerDutyAvailable`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-gcp-projects': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin as `gcpProjectsPlugin` and page as `GcpProjectsPage`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Throw `NotAllowedError` when registering locations with entities of disallowed kinds
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/test-utils': patch
|
||||
---
|
||||
|
||||
Added `mountedRoutes` option to `wrapInTestApp`, allowing routes to be associated to concrete paths to make `useRouteRef` usable in tested components.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-jenkins': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `jenkinsPlugin`, the entity content as `EntityJenkinsContent`, the entity conditional as `isJenkinsAvailable`, and the entity card as `EntityLatestJenkinsRunCard`.
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-fossa': minor
|
||||
---
|
||||
|
||||
Port FOSSA plugin to new extension model.
|
||||
|
||||
If you are using the FOSSA plugin adjust the plugin import from `plugin` to
|
||||
`fossaPlugin` and replace `<FossaCard entity={...} ...>` with `<EntityFossaCard />`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Fix whitespace around variable in unregister error dialog box
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-api-docs': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin as `apiDocsPlugin`, index page as `ApiExplorerPage`, and entity page cards as `EntityApiDefinitionCard`, `EntityConsumedApisCard`, `EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Finalize migration to new composability API, with the plugin instance now exported `catalogPlugin`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-github-actions': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `githubActionsPlugin`, the entity content as `EntityGithubActionsContent`, entity conditional as `isGithubActionsAvailable`, and entity cards as `EntityLatestGithubActionRunCard`, `EntityLatestGithubActionsForBranchCard`, and `EntityRecentGithubActionsRunsCard`.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-tech-radar': patch
|
||||
---
|
||||
|
||||
Fix mapping RadarEntry and Entry for moved and url attributes
|
||||
Fix clicking of links in the radar legend
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-newrelic': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `newRelicPlugin`, and the root page as `NewRelicPage`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Fix check that determines whether popup was closed or the messaging was misconfigured.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Implement `UrlReader.search` which implements glob matching.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `kubernetesPlugin` and entity content as `EntityKubernetesContent`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-sonarqube': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin as `sonarQubePlugin` and card as `EntitySonarQubeCard`.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/catalog-model': patch
|
||||
---
|
||||
|
||||
Introduce json schema variants of the `yup` validation schemas
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kafka': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `kafkaPlugin`, entity content as `EntityKafkaContent`, and entity conditional as `isKafkaAvailable`.
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
# Repo visibility for GitLab and BitBucket repos
|
||||
|
||||
**NOTE: This changes default repo visibility from `private` to `public` for GitLab and BitBucket** which
|
||||
is consistent with the GitHub default. If you were counting on `private` visibility, you'll need to update
|
||||
your scaffolder config to use `private`.
|
||||
|
||||
This adds repo visibility feature parity with GitHub for GitLab and BitBucket.
|
||||
|
||||
To configure the repo visibility, set scaffolder._type_.visibility as in this example:
|
||||
|
||||
```yaml
|
||||
scaffolder:
|
||||
github:
|
||||
visibility: private # 'public' or 'internal' or 'private' (default is 'public')
|
||||
gitlab:
|
||||
visibility: public # 'public' or 'internal' or 'private' (default is 'public')
|
||||
bitbucket:
|
||||
visibility: public # 'public' or 'private' (default is 'public')
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-welcome': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin as `welcomePlugin` and the page as `WelcomePage`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Refactored route response handling to use more explicit types and throw errors.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/techdocs-common': patch
|
||||
---
|
||||
|
||||
dir preparer will use URL Reader in its implementation.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Enhance API calls to support trapping 500 errors from techdocs-backend
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-user-settings': patch
|
||||
---
|
||||
|
||||
Keep the Pin Sidebar setting visible on small screens.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Fixed type inference of `createRouteRef`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-gitops-profiles': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `gitopsProfilesPlugin` and pages as `GitopsProfilesClusterListPage`, `GitopsProfilesClusterPage`, and `GitopsProfilesCreatePage`.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-api-docs': patch
|
||||
'@backstage/plugin-catalog-import': patch
|
||||
'@backstage/plugin-circleci': patch
|
||||
'@backstage/plugin-cloudbuild': patch
|
||||
'@backstage/plugin-github-actions': patch
|
||||
'@backstage/plugin-jenkins': patch
|
||||
'@backstage/plugin-kafka': patch
|
||||
'@backstage/plugin-lighthouse': patch
|
||||
'@backstage/plugin-org': patch
|
||||
'@backstage/plugin-register-component': patch
|
||||
'@backstage/plugin-rollbar': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
'@backstage/plugin-search': patch
|
||||
'@backstage/plugin-sentry': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
'@backstage/dev-utils': patch
|
||||
---
|
||||
|
||||
Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`.
|
||||
@@ -1,61 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
The scaffolder is updated to generate a unique workspace directory inside the temp folder. This directory is cleaned up by the job processor after each run.
|
||||
|
||||
The prepare/template/publish steps have been refactored to operate on known directories, `template/` and `result/`, inside the temporary workspace path.
|
||||
|
||||
Updated preparers to accept the template url instead of the entire template. This is done primarily to allow for backwards compatibility between v1 and v2 scaffolder templates.
|
||||
|
||||
Fixes broken GitHub actions templating in the Create React App template.
|
||||
|
||||
#### For those with **custom** preparers, templates, or publishers
|
||||
|
||||
The preparer interface has changed, the prepare method now only takes a single argument, and doesn't return anything. As part of this change the preparers were refactored to accept a URL pointing to the target directory, rather than computing that from the template entity.
|
||||
|
||||
The `workingDirectory` option was also removed, and replaced with a `workspacePath` option. The difference between the two is that `workingDirectory` was a place for the preparer to create temporary directories, while the `workspacePath` is the specific folder were the entire templating process for a single template job takes place. Instead of returning a path to the folder were the prepared contents were placed, the contents are put at the `<workspacePath>/template` path.
|
||||
|
||||
```diff
|
||||
type PreparerOptions = {
|
||||
- workingDirectory?: string;
|
||||
+ /**
|
||||
+ * Full URL to the directory containg template data
|
||||
+ */
|
||||
+ url: string;
|
||||
+ /**
|
||||
+ * The workspace path that will eventually be the the root of the new repo
|
||||
+ */
|
||||
+ workspacePath: string;
|
||||
logger: Logger;
|
||||
};
|
||||
|
||||
-prepare(template: TemplateEntityV1alpha1, opts?: PreparerOptions): Promise<string>
|
||||
+prepare(opts: PreparerOptions): Promise<void>;
|
||||
```
|
||||
|
||||
Instead of returning a path to the folder were the templaters contents were placed, the contents are put at the `<workspacePath>/result` path. All templaters now also expect the source template to be present in the `template` directory within the `workspacePath`.
|
||||
|
||||
```diff
|
||||
export type TemplaterRunOptions = {
|
||||
- directory: string;
|
||||
+ workspacePath: string;
|
||||
values: TemplaterValues;
|
||||
logStream?: Writable;
|
||||
dockerClient: Docker;
|
||||
};
|
||||
|
||||
-public async run(options: TemplaterRunOptions): Promise<TemplaterRunResult>
|
||||
+public async run(options: TemplaterRunOptions): Promise<void>
|
||||
```
|
||||
|
||||
Just like the preparer and templaters, the publishers have also switched to using `workspacePath`. The root of the new repo is expected to be located at `<workspacePath>/result`.
|
||||
|
||||
```diff
|
||||
export type PublisherOptions = {
|
||||
values: TemplaterValues;
|
||||
- directory: string;
|
||||
+ workspacePath: string;
|
||||
logger: Logger;
|
||||
};
|
||||
```
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': minor
|
||||
'@backstage/create-app': minor
|
||||
---
|
||||
|
||||
`@backstage/plugin-catalog` stopped exporting hooks and helpers for other
|
||||
plugins. They are migrated to `@backstage/plugin-catalog-react`.
|
||||
Change both your dependencies and imports to the new package.
|
||||
@@ -249,6 +249,7 @@ transpilation
|
||||
Tuite
|
||||
ui
|
||||
unmanaged
|
||||
unregister
|
||||
untracked
|
||||
upvote
|
||||
url
|
||||
|
||||
@@ -3,6 +3,7 @@ name: E2E Test Linux
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.changeset/**'
|
||||
- 'contrib/**'
|
||||
- 'docs/**'
|
||||
- 'microsite/**'
|
||||
|
||||
@@ -73,6 +73,9 @@ organization:
|
||||
name: My Company
|
||||
|
||||
# Reference documentation http://backstage.io/docs/features/techdocs/configuration
|
||||
# Note: After experimenting with basic setup, use CI/CD to generate docs
|
||||
# and an external cloud storage when deploying TechDocs for production use-case.
|
||||
# https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach
|
||||
techdocs:
|
||||
builder: 'local' # Alternatives - 'external'
|
||||
generators:
|
||||
|
||||
@@ -24,12 +24,18 @@ Backstage ecosystem.
|
||||
|
||||
## Project roadmap
|
||||
|
||||
| Version | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Backstage Search V.0 ✅ | Search Frontend letting you search through the entities of the software catalog. [See V.0 Use Cases.](#backstage-search-v0) |
|
||||
| Backstage Search V.1 ⌛ | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See V.1 Use Cases.](#backstage-search-v1) |
|
||||
| Backstage Search V.2 ⌛ | Search Backend responsible for the indexing process of entities, and their metadata, registered to the Software Catalog. [See V.2 Use Cases.](#backstage-search-v2) |
|
||||
| Backstage Search V.3 ⌛ | Standardized Search API lets you index other plugins data to the search engine of choice. [See V.3 Use Cases.](#backstage-search-v3) |
|
||||
| Version | Description |
|
||||
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Backstage Search v0 ✅ | Search Frontend letting you search through the entities of the software catalog. [See v0 Use Cases.](#backstage-search-v0) |
|
||||
| [Backstage Search V0.5 ⌛][v0.5] | Foundations for the architecture. |
|
||||
| [Backstage Search v1 ⌛][v1] | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See v1 Use Cases.](#backstage-search-v1) |
|
||||
| [Backstage Search v2 ⌛][v2] | Search Backend responsible for the indexing process of entities, and their metadata, registered to the Software Catalog. [See v2 Use Cases.](#backstage-search-v2) |
|
||||
| [Backstage Search v3 ⌛][v3] | Standardized Search API lets you index other plugins data to the search engine of choice. [See v3 Use Cases.](#backstage-search-v3) |
|
||||
|
||||
[v0.5]: https://github.com/backstage/backstage/milestone/25
|
||||
[v1]: https://github.com/backstage/backstage/milestone/26
|
||||
[v2]: https://github.com/backstage/backstage/milestone/27
|
||||
[v3]: https://github.com/backstage/backstage/milestone/28
|
||||
|
||||
## Use Cases
|
||||
|
||||
|
||||
@@ -80,6 +80,10 @@ Create a `/docs` folder in the root of the project with at least an `index.md`
|
||||
file. _(If you add more markdown files, make sure to update the nav in the
|
||||
mkdocs.yml file to get a proper navigation for your documentation.)_
|
||||
|
||||
> Note - Although `docs` is a popular directory name for storing documentation,
|
||||
> it can be renamed to something else and can be configured by `mkdocs.yml`. See
|
||||
> https://www.mkdocs.org/user-guide/configuration/#docs_dir
|
||||
|
||||
The `docs/index.md` can for example have the following content:
|
||||
|
||||
```md
|
||||
|
||||
@@ -90,7 +90,7 @@ $ docker build -t example-deployment .
|
||||
To run the image locally you can run:
|
||||
|
||||
```sh
|
||||
$ docker run -p -it 7000:7000 example-deployment
|
||||
$ docker run -it -p 7000:7000 example-deployment
|
||||
```
|
||||
|
||||
You should then start to get logs in your terminal, and then you can open your
|
||||
|
||||
@@ -384,11 +384,3 @@ A frontend plugin that provides a page where the user can tweak various
|
||||
settings.
|
||||
|
||||
Stability: `1`
|
||||
|
||||
### `welcome` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/welcome/)
|
||||
|
||||
A plugin that can be used to welcome the user to Backstage.
|
||||
|
||||
Stability: `0`. This used to be the start page for the example app, but has been
|
||||
replaced by the catalog plugin. It is still viewable at `/welcome` but may be
|
||||
removed.
|
||||
|
||||
@@ -14,7 +14,7 @@ Here's a code sample:
|
||||
import { createPlugin } from '@backstage/core';
|
||||
|
||||
export default createPlugin({
|
||||
id: 'welcome',
|
||||
id: 'plugin-name',
|
||||
register({ featureFlags }) {
|
||||
featureFlags.register('enable-example-feature');
|
||||
},
|
||||
|
||||
@@ -1115,6 +1115,10 @@ code {
|
||||
font-size: 115%;
|
||||
}
|
||||
|
||||
.post pre {
|
||||
color: #e4e4e4;
|
||||
}
|
||||
|
||||
.Block__GIF {
|
||||
width: 190px;
|
||||
height: auto;
|
||||
|
||||
+2
-1
@@ -37,11 +37,12 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"**/@roadiehq/**/@backstage/core": "*",
|
||||
"**/@roadiehq/**/@backstage/plugin-catalog": "*",
|
||||
"**/@roadiehq/**/@backstage/catalog-model": "*"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.11.0",
|
||||
"@changesets/cli": "^2.14.0",
|
||||
"@octokit/openapi-types": "^2.2.0",
|
||||
"@spotify/eslint-config-oss": "^1.0.1",
|
||||
"@spotify/prettier-config": "^9.0.0",
|
||||
|
||||
@@ -1,5 +1,81 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9d6ef14bc: Migrated to new composability API, exporting the plugin instance as `rollbarPlugin`, the entity page content as `EntityRollbarContent`, and entity conditional as `isRollbarAvailable`. Updated the `EntityPage` for the `example-app` to include a composite `ErrorsSwitcher` component that works with both `Sentry` & `Rollbar`. Also removed the unused and undocumented `RollbarHome` related components.
|
||||
- Updated dependencies [ceef4dd89]
|
||||
- Updated dependencies [720149854]
|
||||
- Updated dependencies [19172f5a9]
|
||||
- Updated dependencies [4c6a6dddd]
|
||||
- Updated dependencies [398e1f83e]
|
||||
- Updated dependencies [87b189d00]
|
||||
- Updated dependencies [12ece98cd]
|
||||
- Updated dependencies [d82246867]
|
||||
- Updated dependencies [7fc89bae2]
|
||||
- Updated dependencies [c810082ae]
|
||||
- Updated dependencies [b712841d6]
|
||||
- Updated dependencies [a5628df40]
|
||||
- Updated dependencies [5fa3bdb55]
|
||||
- Updated dependencies [bc5082a00]
|
||||
- Updated dependencies [6e612ce25]
|
||||
- Updated dependencies [e44925723]
|
||||
- Updated dependencies [b37501a3d]
|
||||
- Updated dependencies [9d6ef14bc]
|
||||
- Updated dependencies [025e122c3]
|
||||
- Updated dependencies [e9aab60c7]
|
||||
- Updated dependencies [21e624ba9]
|
||||
- Updated dependencies [0269f4fd9]
|
||||
- Updated dependencies [19fe61c27]
|
||||
- Updated dependencies [da9f53c60]
|
||||
- Updated dependencies [a08c4b0b0]
|
||||
- Updated dependencies [bc5082a00]
|
||||
- Updated dependencies [bc5082a00]
|
||||
- Updated dependencies [b37501a3d]
|
||||
- Updated dependencies [90c8f20b9]
|
||||
- Updated dependencies [32c95605f]
|
||||
- Updated dependencies [7881f2117]
|
||||
- Updated dependencies [8dfdec613]
|
||||
- Updated dependencies [54c7d02f7]
|
||||
- Updated dependencies [de98c32ed]
|
||||
- Updated dependencies [806929fe2]
|
||||
- Updated dependencies [019fe39a0]
|
||||
- Updated dependencies [019fe39a0]
|
||||
- Updated dependencies [11cb5ef94]
|
||||
- @backstage/plugin-catalog-import@0.3.7
|
||||
- @backstage/plugin-scaffolder@0.4.2
|
||||
- @backstage/plugin-cost-insights@0.8.0
|
||||
- @backstage/cli@0.6.0
|
||||
- @backstage/plugin-graphiql@0.2.7
|
||||
- @backstage/core@0.6.0
|
||||
- @backstage/plugin-api-docs@0.4.4
|
||||
- @backstage/plugin-catalog@0.3.0
|
||||
- @backstage/plugin-catalog-react@0.0.2
|
||||
- @backstage/plugin-org@0.3.5
|
||||
- @backstage/theme@0.2.3
|
||||
- @backstage/plugin-lighthouse@0.2.9
|
||||
- @backstage/plugin-techdocs@0.5.5
|
||||
- @backstage/plugin-user-settings@0.2.5
|
||||
- @backstage/catalog-model@0.7.1
|
||||
- @backstage/plugin-rollbar@0.2.9
|
||||
- @backstage/plugin-gcp-projects@0.2.4
|
||||
- @backstage/plugin-tech-radar@0.3.4
|
||||
- @backstage/plugin-welcome@0.2.5
|
||||
- @backstage/plugin-explore@0.2.4
|
||||
- @backstage/plugin-circleci@0.2.7
|
||||
- @backstage/plugin-cloudbuild@0.2.8
|
||||
- @backstage/plugin-github-actions@0.3.1
|
||||
- @backstage/plugin-jenkins@0.3.8
|
||||
- @backstage/plugin-kafka@0.2.1
|
||||
- @backstage/plugin-register-component@0.2.8
|
||||
- @backstage/plugin-search@0.2.7
|
||||
- @backstage/plugin-sentry@0.3.4
|
||||
- @backstage/plugin-gitops-profiles@0.2.4
|
||||
- @backstage/plugin-kubernetes@0.3.8
|
||||
- @backstage/plugin-newrelic@0.2.4
|
||||
- @backstage/plugin-pagerduty@0.2.7
|
||||
|
||||
## 0.2.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+32
-33
@@ -1,41 +1,40 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.13",
|
||||
"version": "0.2.14",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.0",
|
||||
"@backstage/cli": "^0.5.0",
|
||||
"@backstage/core": "^0.5.0",
|
||||
"@backstage/plugin-api-docs": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.14",
|
||||
"@backstage/plugin-catalog-react": "^0.0.1",
|
||||
"@backstage/plugin-catalog-import": "^0.3.6",
|
||||
"@backstage/plugin-circleci": "^0.2.6",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.7",
|
||||
"@backstage/plugin-cost-insights": "^0.7.0",
|
||||
"@backstage/plugin-explore": "^0.2.3",
|
||||
"@backstage/plugin-gcp-projects": "^0.2.3",
|
||||
"@backstage/plugin-github-actions": "^0.3.0",
|
||||
"@backstage/plugin-gitops-profiles": "^0.2.3",
|
||||
"@backstage/plugin-graphiql": "^0.2.6",
|
||||
"@backstage/plugin-org": "^0.3.4",
|
||||
"@backstage/plugin-jenkins": "^0.3.6",
|
||||
"@backstage/plugin-kafka": "^0.2.0",
|
||||
"@backstage/plugin-kubernetes": "^0.3.7",
|
||||
"@backstage/plugin-lighthouse": "^0.2.8",
|
||||
"@backstage/plugin-newrelic": "^0.2.3",
|
||||
"@backstage/plugin-pagerduty": "0.2.6",
|
||||
"@backstage/plugin-register-component": "^0.2.7",
|
||||
"@backstage/plugin-rollbar": "^0.2.8",
|
||||
"@backstage/plugin-scaffolder": "^0.4.1",
|
||||
"@backstage/plugin-sentry": "^0.3.3",
|
||||
"@backstage/plugin-search": "^0.2.6",
|
||||
"@backstage/plugin-tech-radar": "^0.3.3",
|
||||
"@backstage/plugin-techdocs": "^0.5.4",
|
||||
"@backstage/plugin-user-settings": "^0.2.4",
|
||||
"@backstage/plugin-welcome": "^0.2.4",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@backstage/catalog-model": "^0.7.1",
|
||||
"@backstage/cli": "^0.6.0",
|
||||
"@backstage/core": "^0.6.0",
|
||||
"@backstage/plugin-api-docs": "^0.4.4",
|
||||
"@backstage/plugin-catalog": "^0.3.0",
|
||||
"@backstage/plugin-catalog-react": "^0.0.2",
|
||||
"@backstage/plugin-catalog-import": "^0.3.7",
|
||||
"@backstage/plugin-circleci": "^0.2.7",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.8",
|
||||
"@backstage/plugin-cost-insights": "^0.8.0",
|
||||
"@backstage/plugin-explore": "^0.2.4",
|
||||
"@backstage/plugin-gcp-projects": "^0.2.4",
|
||||
"@backstage/plugin-github-actions": "^0.3.1",
|
||||
"@backstage/plugin-gitops-profiles": "^0.2.4",
|
||||
"@backstage/plugin-graphiql": "^0.2.7",
|
||||
"@backstage/plugin-org": "^0.3.5",
|
||||
"@backstage/plugin-jenkins": "^0.3.8",
|
||||
"@backstage/plugin-kafka": "^0.2.1",
|
||||
"@backstage/plugin-kubernetes": "^0.3.8",
|
||||
"@backstage/plugin-lighthouse": "^0.2.9",
|
||||
"@backstage/plugin-newrelic": "^0.2.4",
|
||||
"@backstage/plugin-pagerduty": "0.2.7",
|
||||
"@backstage/plugin-register-component": "^0.2.8",
|
||||
"@backstage/plugin-rollbar": "^0.2.9",
|
||||
"@backstage/plugin-scaffolder": "^0.4.2",
|
||||
"@backstage/plugin-sentry": "^0.3.4",
|
||||
"@backstage/plugin-search": "^0.2.7",
|
||||
"@backstage/plugin-tech-radar": "^0.3.4",
|
||||
"@backstage/plugin-techdocs": "^0.5.5",
|
||||
"@backstage/plugin-user-settings": "^0.2.5",
|
||||
"@backstage/theme": "^0.2.3",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@octokit/rest": "^18.0.12",
|
||||
|
||||
+15
-14
@@ -15,30 +15,30 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
createApp,
|
||||
AlertDisplay,
|
||||
OAuthRequestDialog,
|
||||
SignInPage,
|
||||
createApp,
|
||||
createRouteRef,
|
||||
FlatRoutes,
|
||||
OAuthRequestDialog,
|
||||
SignInPage,
|
||||
} from '@backstage/core';
|
||||
import React from 'react';
|
||||
import Root from './components/Root';
|
||||
import * as plugins from './plugins';
|
||||
import { apis } from './apis';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { providers } from './identityProviders';
|
||||
import { Router as CatalogRouter } from '@backstage/plugin-catalog';
|
||||
import { Router as DocsRouter } from '@backstage/plugin-techdocs';
|
||||
import { Router as ImportComponentRouter } from '@backstage/plugin-catalog-import';
|
||||
import { ExplorePage } from '@backstage/plugin-explore';
|
||||
import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql';
|
||||
import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar';
|
||||
import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse';
|
||||
import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component';
|
||||
import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar';
|
||||
import { Router as DocsRouter } from '@backstage/plugin-techdocs';
|
||||
import { Router as SettingsRouter } from '@backstage/plugin-user-settings';
|
||||
import { Router as ImportComponentRouter } from '@backstage/plugin-catalog-import';
|
||||
import { Route, Navigate } from 'react-router';
|
||||
|
||||
import React from 'react';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { Navigate, Route } from 'react-router';
|
||||
import { apis } from './apis';
|
||||
import { EntityPage } from './components/catalog/EntityPage';
|
||||
import Root from './components/Root';
|
||||
import { providers } from './identityProviders';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
const app = createApp({
|
||||
apis,
|
||||
@@ -78,6 +78,7 @@ const routes = (
|
||||
element={<CatalogRouter EntityPage={EntityPage} />}
|
||||
/>
|
||||
<Route path="/docs" element={<DocsRouter />} />
|
||||
<Route path="/explore" element={<ExplorePage />} />
|
||||
<Route
|
||||
path="/tech-radar"
|
||||
element={<TechRadarRouter width={1500} height={800} />}
|
||||
|
||||
@@ -20,6 +20,7 @@ import HomeIcon from '@material-ui/icons/Home';
|
||||
import ExtensionIcon from '@material-ui/icons/Extension';
|
||||
import RuleIcon from '@material-ui/icons/AssignmentTurnedIn';
|
||||
import MapIcon from '@material-ui/icons/MyLocation';
|
||||
import LayersIcon from '@material-ui/icons/Layers';
|
||||
import LibraryBooks from '@material-ui/icons/LibraryBooks';
|
||||
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
|
||||
import MoneyIcon from '@material-ui/icons/MonetizationOn';
|
||||
@@ -82,6 +83,7 @@ const Root = ({ children }: PropsWithChildren<{}>) => (
|
||||
<SidebarItem icon={HomeIcon} to="/catalog" text="Home" />
|
||||
<SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
|
||||
<SidebarItem icon={LibraryBooks} to="docs" text="Docs" />
|
||||
<SidebarItem icon={LayersIcon} to="explore" text="Explore" />
|
||||
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
|
||||
{/* End global nav */}
|
||||
<SidebarDivider />
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ApiEntity,
|
||||
Entity,
|
||||
@@ -64,6 +65,10 @@ import {
|
||||
isPluginApplicableToEntity as isPagerDutyAvailable,
|
||||
PagerDutyCard,
|
||||
} from '@backstage/plugin-pagerduty';
|
||||
import {
|
||||
isRollbarAvailable,
|
||||
Router as RollbarRouter,
|
||||
} from '@backstage/plugin-rollbar';
|
||||
import { Router as SentryRouter } from '@backstage/plugin-sentry';
|
||||
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
|
||||
import { Button, Grid } from '@material-ui/core';
|
||||
@@ -153,6 +158,15 @@ const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const ErrorsSwitcher = ({ entity }: { entity: Entity }) => {
|
||||
switch (true) {
|
||||
case isRollbarAvailable(entity):
|
||||
return <RollbarRouter entity={entity} />;
|
||||
default:
|
||||
return <SentryRouter entity={entity} />;
|
||||
}
|
||||
};
|
||||
|
||||
const ComponentOverviewContent = ({ entity }: { entity: Entity }) => (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
@@ -212,9 +226,9 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
element={<CICDSwitcher entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/sentry"
|
||||
title="Sentry"
|
||||
element={<SentryRouter entity={entity} />}
|
||||
path="/errors/*"
|
||||
title="Errors"
|
||||
element={<ErrorsSwitcher entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/api/*"
|
||||
@@ -267,9 +281,9 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
element={<LighthouseRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/sentry"
|
||||
title="Sentry"
|
||||
element={<SentryRouter entity={entity} />}
|
||||
path="/errors/*"
|
||||
title="Errors"
|
||||
element={<ErrorsSwitcher entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/docs/*"
|
||||
|
||||
@@ -13,12 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { plugin as WelcomePlugin } from '@backstage/plugin-welcome';
|
||||
export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse';
|
||||
export { plugin as CatalogPlugin } from '@backstage/plugin-catalog';
|
||||
export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder';
|
||||
export { plugin as TechRadar } from '@backstage/plugin-tech-radar';
|
||||
export { plugin as Explore } from '@backstage/plugin-explore';
|
||||
export { explorePlugin } from '@backstage/plugin-explore';
|
||||
export { plugin as Circleci } from '@backstage/plugin-circleci';
|
||||
export { plugin as RegisterComponent } from '@backstage/plugin-register-component';
|
||||
export { plugin as Sentry } from '@backstage/plugin-sentry';
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2430ee7c2: Updated the `rootLogger` in `@backstage/backend-common` to support custom logging options. This is useful when you want to make some changes without re-implementing the entire logger and calling `setRootLogger` or `logger.configure`. For example you can add additional `defaultMeta` tags to each log entry. The following changes are included:
|
||||
|
||||
- Added `createRootLogger` which accepts winston `LoggerOptions`. These options allow overriding the default keys.
|
||||
|
||||
Example Usage:
|
||||
|
||||
```ts
|
||||
// Create the logger
|
||||
const logger = createRootLogger({
|
||||
defaultMeta: { appName: 'backstage', appEnv: 'prod' },
|
||||
});
|
||||
|
||||
// Add a custom logger transport
|
||||
logger.add(new MyCustomTransport());
|
||||
|
||||
const config = await loadBackendConfig({
|
||||
argv: process.argv,
|
||||
logger: getRootLogger(), // already set to new logger instance
|
||||
});
|
||||
```
|
||||
|
||||
- Updated dependencies [c4abcdb60]
|
||||
- Updated dependencies [062df71db]
|
||||
- Updated dependencies [064c513e1]
|
||||
- Updated dependencies [e9aab60c7]
|
||||
- Updated dependencies [3149bfe63]
|
||||
- Updated dependencies [2e62aea6f]
|
||||
- @backstage/integration@0.3.2
|
||||
- @backstage/config-loader@0.5.1
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -31,8 +31,9 @@
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.1",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/config-loader": "^0.5.0",
|
||||
"@backstage/integration": "^0.3.1",
|
||||
"@backstage/config-loader": "^0.5.1",
|
||||
"@backstage/integration": "^0.3.2",
|
||||
"@octokit/rest": "^18.0.12",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@types/express": "^4.17.6",
|
||||
"archiver": "^5.0.2",
|
||||
@@ -49,6 +50,7 @@
|
||||
"knex": "^0.21.6",
|
||||
"lodash": "^4.17.15",
|
||||
"logform": "^2.1.1",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimist": "^1.2.5",
|
||||
"morgan": "^1.10.0",
|
||||
"selfsigned": "^1.10.7",
|
||||
@@ -66,7 +68,7 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.5.0",
|
||||
"@backstage/cli": "^0.6.0",
|
||||
"@backstage/test-utils": "^0.1.5",
|
||||
"@types/archiver": "^5.1.0",
|
||||
"@types/compression": "^1.7.0",
|
||||
|
||||
@@ -63,10 +63,10 @@ export function errorHandler(
|
||||
return (
|
||||
error: Error,
|
||||
_request: Request,
|
||||
response: Response,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) => {
|
||||
if (response.headersSent) {
|
||||
if (res.headersSent) {
|
||||
// If the headers have already been sent, do not send the response again
|
||||
// as this will throw an error in the backend.
|
||||
next(error);
|
||||
@@ -80,7 +80,9 @@ export function errorHandler(
|
||||
logger.error(error);
|
||||
}
|
||||
|
||||
response.status(status).send(message);
|
||||
res.status(status);
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.send(message);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
@@ -116,6 +117,10 @@ export class AzureUrlReader implements UrlReader {
|
||||
});
|
||||
}
|
||||
|
||||
async search(): Promise<SearchResponse> {
|
||||
throw new Error('AzureUrlReader does not implement search');
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host, token } = this.options;
|
||||
return `azure{host=${host},authed=${Boolean(token)}}`;
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
|
||||
@@ -129,6 +130,10 @@ export class BitbucketUrlReader implements UrlReader {
|
||||
});
|
||||
}
|
||||
|
||||
async search(): Promise<SearchResponse> {
|
||||
throw new Error('BitbucketUrlReader does not implement search');
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host, token, username, appPassword } = this.config;
|
||||
let authed = Boolean(token);
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
|
||||
import fetch from 'cross-fetch';
|
||||
import { NotFoundError } from '../errors';
|
||||
import { ReaderFactory, ReadTreeResponse, UrlReader } from './types';
|
||||
import {
|
||||
ReaderFactory,
|
||||
ReadTreeResponse,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* A UrlReader that does a plain fetch of the URL.
|
||||
@@ -68,10 +73,14 @@ export class FetchUrlReader implements UrlReader {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
readTree(): Promise<ReadTreeResponse> {
|
||||
async readTree(): Promise<ReadTreeResponse> {
|
||||
throw new Error('FetchUrlReader does not implement readTree');
|
||||
}
|
||||
|
||||
async search(): Promise<SearchResponse> {
|
||||
throw new Error('FetchUrlReader does not implement search');
|
||||
}
|
||||
|
||||
toString() {
|
||||
return 'fetch{}';
|
||||
}
|
||||
|
||||
@@ -23,7 +23,13 @@ import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import path from 'path';
|
||||
import { NotFoundError, NotModifiedError } from '../errors';
|
||||
import { GithubUrlReader } from './GithubUrlReader';
|
||||
import {
|
||||
GhBlobResponse,
|
||||
GhBranchResponse,
|
||||
GhRepoResponse,
|
||||
GhTreeResponse,
|
||||
GithubUrlReader,
|
||||
} from './GithubUrlReader';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
|
||||
const treeResponseFactory = ReadTreeResponseFactory.create({
|
||||
@@ -69,6 +75,10 @@ describe('GithubUrlReader', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* read
|
||||
*/
|
||||
|
||||
describe('read', () => {
|
||||
it('should use the headers from the credentials provider to the fetch request when doing read', async () => {
|
||||
expect.assertions(2);
|
||||
@@ -107,6 +117,10 @@ describe('GithubUrlReader', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* readTree
|
||||
*/
|
||||
|
||||
describe('readTree', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
@@ -128,29 +142,31 @@ describe('GithubUrlReader', () => {
|
||||
);
|
||||
|
||||
const reposGithubApiResponse = {
|
||||
id: '123',
|
||||
id: 123,
|
||||
full_name: 'backstage/mock',
|
||||
default_branch: 'main',
|
||||
branches_url:
|
||||
'https://api.github.com/repos/backstage/mock/branches{/branch}',
|
||||
archive_url:
|
||||
'https://api.github.com/repos/backstage/mock/{archive_format}{/ref}',
|
||||
};
|
||||
} as Partial<GhRepoResponse>;
|
||||
|
||||
const reposGheApiResponse = {
|
||||
...reposGithubApiResponse,
|
||||
id: 123,
|
||||
full_name: 'backstage/mock',
|
||||
default_branch: 'main',
|
||||
branches_url:
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}',
|
||||
archive_url:
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}',
|
||||
};
|
||||
} as Partial<GhRepoResponse>;
|
||||
|
||||
const branchesApiResponse = {
|
||||
name: 'main',
|
||||
commit: {
|
||||
sha: 'etag123abc',
|
||||
},
|
||||
};
|
||||
} as Partial<GhBranchResponse>;
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
@@ -392,4 +408,359 @@ describe('GithubUrlReader', () => {
|
||||
}).toThrowError('must configure an explicit apiBaseUrl');
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* search
|
||||
*/
|
||||
|
||||
describe('search', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({ '/tmp': mockFs.directory() });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
const repoBuffer = fs.readFileSync(
|
||||
path.resolve(
|
||||
'src',
|
||||
'reading',
|
||||
'__fixtures__',
|
||||
'backstage-mock-etag123.tar.gz',
|
||||
),
|
||||
);
|
||||
|
||||
const githubTreeContents: GhTreeResponse['tree'] = [
|
||||
{
|
||||
path: 'mkdocs.yml',
|
||||
type: 'blob',
|
||||
url: 'https://api.github.com/repos/backstage/mock/git/blobs/1',
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
type: 'tree',
|
||||
url: 'https://api.github.com/repos/backstage/mock/git/trees/2',
|
||||
},
|
||||
{
|
||||
path: 'docs/index.md',
|
||||
type: 'blob',
|
||||
url: 'https://api.github.com/repos/backstage/mock/git/blobs/3',
|
||||
},
|
||||
];
|
||||
|
||||
const gheTreeContents: GhTreeResponse['tree'] = [
|
||||
{
|
||||
path: 'mkdocs.yml',
|
||||
type: 'blob',
|
||||
url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/1',
|
||||
},
|
||||
{
|
||||
path: 'docs',
|
||||
type: 'tree',
|
||||
url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/2',
|
||||
},
|
||||
{
|
||||
path: 'docs/index.md',
|
||||
type: 'blob',
|
||||
url: 'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/3',
|
||||
},
|
||||
];
|
||||
|
||||
// Tarballs
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/tarball/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/x-gzip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=backstage-mock-etag123.tar.gz',
|
||||
),
|
||||
ctx.body(repoBuffer),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/tarball/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/x-gzip'),
|
||||
ctx.set(
|
||||
'content-disposition',
|
||||
'attachment; filename=backstage-mock-etag123.tar.gz',
|
||||
),
|
||||
ctx.body(repoBuffer),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// Repo details
|
||||
beforeEach(() => {
|
||||
const githubResponse = {
|
||||
id: 123,
|
||||
full_name: 'backstage/mock',
|
||||
default_branch: 'main',
|
||||
branches_url:
|
||||
'https://api.github.com/repos/backstage/mock/branches{/branch}',
|
||||
archive_url:
|
||||
'https://api.github.com/repos/backstage/mock/{archive_format}{/ref}',
|
||||
trees_url:
|
||||
'https://api.github.com/repos/backstage/mock/git/trees{/sha}',
|
||||
} as Partial<GhRepoResponse>;
|
||||
|
||||
const gheResponse = {
|
||||
id: 123,
|
||||
full_name: 'backstage/mock',
|
||||
default_branch: 'main',
|
||||
branches_url:
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}',
|
||||
archive_url:
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}',
|
||||
trees_url:
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees{/sha}',
|
||||
} as Partial<GhRepoResponse>;
|
||||
|
||||
worker.use(
|
||||
rest.get('https://api.github.com/repos/backstage/mock', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(githubResponse),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(gheResponse),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// Branch details
|
||||
beforeEach(() => {
|
||||
const response = {
|
||||
name: 'main',
|
||||
commit: {
|
||||
sha: 'etag123abc',
|
||||
},
|
||||
} as Partial<GhBranchResponse>;
|
||||
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/branches/main',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(response),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/branches/main',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(response),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/branches/branchDoesNotExist',
|
||||
(_, res, ctx) => res(ctx.status(404)),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// Blobs
|
||||
beforeEach(() => {
|
||||
const blob1Response = {
|
||||
content: Buffer.from('site_name: Test\n').toString('base64'),
|
||||
} as Partial<GhBlobResponse>;
|
||||
|
||||
const blob3Response = {
|
||||
content: Buffer.from('# Test\n').toString('base64'),
|
||||
} as Partial<GhBlobResponse>;
|
||||
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/git/blobs/1',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(blob1Response),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/git/blobs/3',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(blob3Response),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/1',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(blob1Response),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/git/blobs/3',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(blob3Response),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
async function runTests(reader: GithubUrlReader, baseUrl: string) {
|
||||
const r1 = await reader.search(
|
||||
`${baseUrl}/backstage/mock/tree/main/**/*`,
|
||||
);
|
||||
expect(r1.etag).toBe('etag123abc');
|
||||
expect(r1.files.length).toBe(2);
|
||||
|
||||
const r2 = await reader.search(
|
||||
`${baseUrl}/backstage/mock/tree/main/**/*`,
|
||||
{ etag: 'somethingElse' },
|
||||
);
|
||||
expect(r2.etag).toBe('etag123abc');
|
||||
expect(r2.files.length).toBe(2);
|
||||
|
||||
const r3 = await reader.search(`${baseUrl}/backstage/mock/tree/main/o`);
|
||||
expect(r3.files.length).toBe(0);
|
||||
|
||||
const r4 = await reader.search(
|
||||
`${baseUrl}/backstage/mock/tree/main/*docs*`,
|
||||
);
|
||||
expect(r4.files.length).toBe(1);
|
||||
expect(r4.files[0].url).toBe(
|
||||
`${baseUrl}/backstage/mock/tree/main/mkdocs.yml`,
|
||||
);
|
||||
await expect(r4.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('site_name: Test\n'),
|
||||
);
|
||||
|
||||
const r5 = await reader.search(
|
||||
`${baseUrl}/backstage/mock/tree/main/*/index.*`,
|
||||
);
|
||||
expect(r5.files.length).toBe(1);
|
||||
expect(r5.files[0].url).toBe(
|
||||
`${baseUrl}/backstage/mock/tree/main/docs/index.md`,
|
||||
);
|
||||
await expect(r5.files[0].content()).resolves.toEqual(
|
||||
Buffer.from('# Test\n'),
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('succeeds on github when going via repo listing', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/git/trees/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json({
|
||||
truncated: false,
|
||||
tree: githubTreeContents,
|
||||
} as Partial<GhTreeResponse>),
|
||||
),
|
||||
),
|
||||
);
|
||||
await runTests(githubProcessor, 'https://github.com');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('succeeds on github when going via readTree', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://api.github.com/repos/backstage/mock/git/trees/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json({
|
||||
truncated: true,
|
||||
tree: [],
|
||||
} as Partial<GhTreeResponse>),
|
||||
),
|
||||
),
|
||||
);
|
||||
await runTests(githubProcessor, 'https://github.com');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('succeeds on ghe when going via repo listing', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json({
|
||||
truncated: false,
|
||||
tree: gheTreeContents,
|
||||
} as Partial<GhTreeResponse>),
|
||||
),
|
||||
),
|
||||
);
|
||||
await runTests(gheProcessor, 'https://ghe.github.com');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it('succeeds on ghe when going via readTree', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json({
|
||||
truncated: true,
|
||||
tree: [],
|
||||
} as Partial<GhTreeResponse>),
|
||||
),
|
||||
),
|
||||
);
|
||||
await runTests(gheProcessor, 'https://ghe.github.com');
|
||||
});
|
||||
|
||||
it('throws NotModifiedError when same etag', async () => {
|
||||
await expect(
|
||||
githubProcessor.search(
|
||||
'https://githib.com/backstage/mock/tree/main/**/*',
|
||||
{ etag: 'etag123abc' },
|
||||
),
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
|
||||
it('throws NotFoundError when missing branch', async () => {
|
||||
await expect(
|
||||
githubProcessor.search(
|
||||
'https://githib.com/backstage/mock/tree/branchDoesNotExist/**/*',
|
||||
),
|
||||
).rejects.toThrow(NotFoundError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
GitHubIntegrationConfig,
|
||||
readGitHubIntegrationConfigs,
|
||||
getGitHubFileFetchUrl,
|
||||
GithubCredentialsProvider,
|
||||
GitHubIntegrationConfig,
|
||||
readGitHubIntegrationConfigs,
|
||||
} from '@backstage/integration';
|
||||
import { RestEndpointMethodTypes } from '@octokit/rest';
|
||||
import fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '../errors';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
@@ -29,9 +31,17 @@ import {
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
SearchOptions,
|
||||
SearchResponse,
|
||||
SearchResponseFile,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
|
||||
export type GhRepoResponse = RestEndpointMethodTypes['repos']['get']['response']['data'];
|
||||
export type GhBranchResponse = RestEndpointMethodTypes['repos']['getBranch']['response']['data'];
|
||||
export type GhTreeResponse = RestEndpointMethodTypes['git']['getTree']['response']['data'];
|
||||
export type GhBlobResponse = RestEndpointMethodTypes['git']['getBlob']['response']['data'];
|
||||
|
||||
/**
|
||||
* A processor that adds the ability to read files from GitHub v3 APIs, such as
|
||||
* the one exposed by GitHub itself.
|
||||
@@ -98,86 +108,190 @@ export class GithubUrlReader implements UrlReader {
|
||||
url: string,
|
||||
options?: ReadTreeOptions,
|
||||
): Promise<ReadTreeResponse> {
|
||||
const { ref, filepath, full_name } = parseGitUrl(url);
|
||||
// Caveat: The ref will totally be incorrect if the branch name includes a /
|
||||
// Thus, readTree can not work on url containing branch name that has a /
|
||||
|
||||
const { headers } = await this.deps.credentialsProvider.getCredentials({
|
||||
url,
|
||||
});
|
||||
|
||||
// Get GitHub API urls for the repository
|
||||
const repoGitHubResponse = await fetch(
|
||||
new URL(`${this.config.apiBaseUrl}/repos/${full_name}`).toString(),
|
||||
{
|
||||
headers,
|
||||
},
|
||||
);
|
||||
if (!repoGitHubResponse.ok) {
|
||||
const message = `Failed to read tree (repository) from ${url}, ${repoGitHubResponse.status} ${repoGitHubResponse.statusText}`;
|
||||
if (repoGitHubResponse.status === 404) {
|
||||
throw new NotFoundError(message);
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const repoResponseJson = await repoGitHubResponse.json();
|
||||
|
||||
// ref is an empty string if no branch is set in provided url to readTree.
|
||||
// Use GitHub API to get the default branch of the repository.
|
||||
const branch = ref || repoResponseJson.default_branch;
|
||||
const branchesApiUrl = repoResponseJson.branches_url;
|
||||
const archiveApiUrl = repoResponseJson.archive_url;
|
||||
|
||||
// Fetch the latest commit in the provided or default branch to compare against
|
||||
// the provided sha.
|
||||
const branchGitHubResponse = await fetch(
|
||||
// branchesApiUrl looks like "https://api.github.com/repos/owner/repo/branches{/branch}"
|
||||
branchesApiUrl.replace('{/branch}', `/${branch}`),
|
||||
{
|
||||
headers,
|
||||
},
|
||||
);
|
||||
if (!branchGitHubResponse.ok) {
|
||||
const message = `Failed to read tree (branch) from ${url}, ${branchGitHubResponse.status} ${branchGitHubResponse.statusText}`;
|
||||
if (branchGitHubResponse.status === 404) {
|
||||
throw new NotFoundError(message);
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
const commitSha = (await branchGitHubResponse.json()).commit.sha;
|
||||
const repoDetails = await this.getRepoDetails(url);
|
||||
const commitSha = repoDetails.branch.commit.sha!;
|
||||
|
||||
if (options?.etag && options.etag === commitSha) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
const archive = await fetch(
|
||||
// archiveApiUrl looks like "https://api.github.com/repos/owner/repo/{archive_format}{/ref}"
|
||||
archiveApiUrl
|
||||
.replace('{archive_format}', 'tarball')
|
||||
.replace('{/ref}', `/${commitSha}`),
|
||||
const { filepath } = parseGitUrl(url);
|
||||
const { headers } = await this.deps.credentialsProvider.getCredentials({
|
||||
url,
|
||||
});
|
||||
|
||||
return this.doReadTree(
|
||||
repoDetails.repo.archive_url,
|
||||
commitSha,
|
||||
filepath,
|
||||
{ headers },
|
||||
options,
|
||||
);
|
||||
if (!archive.ok) {
|
||||
const message = `Failed to read tree (archive) from ${url}, ${archive.status} ${archive.statusText}`;
|
||||
if (archive.status === 404) {
|
||||
throw new NotFoundError(message);
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
async search(url: string, options?: SearchOptions): Promise<SearchResponse> {
|
||||
const repoDetails = await this.getRepoDetails(url);
|
||||
const commitSha = repoDetails.branch.commit.sha!;
|
||||
|
||||
if (options?.etag && options.etag === commitSha) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
// TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want
|
||||
// to stick to using that in exclusively backend code.
|
||||
stream: (archive.body as unknown) as Readable,
|
||||
subpath: filepath,
|
||||
etag: commitSha,
|
||||
filter: options?.filter,
|
||||
const { filepath } = parseGitUrl(url);
|
||||
const { headers } = await this.deps.credentialsProvider.getCredentials({
|
||||
url,
|
||||
});
|
||||
|
||||
const files = await this.doSearch(
|
||||
url,
|
||||
repoDetails.repo.trees_url,
|
||||
repoDetails.repo.archive_url,
|
||||
commitSha,
|
||||
filepath,
|
||||
{ headers },
|
||||
);
|
||||
|
||||
return { files, etag: commitSha };
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host, token } = this.config;
|
||||
return `github{host=${host},authed=${Boolean(token)}}`;
|
||||
}
|
||||
|
||||
private async doReadTree(
|
||||
archiveUrl: string,
|
||||
sha: string,
|
||||
subpath: string,
|
||||
init: RequestInit,
|
||||
options?: ReadTreeOptions,
|
||||
): Promise<ReadTreeResponse> {
|
||||
// archive_url looks like "https://api.github.com/repos/owner/repo/{archive_format}{/ref}"
|
||||
const archive = await this.fetchResponse(
|
||||
archiveUrl
|
||||
.replace('{archive_format}', 'tarball')
|
||||
.replace('{/ref}', `/${sha}`),
|
||||
init,
|
||||
);
|
||||
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
// TODO(Rugvip): Underlying implementation of fetch will be node-fetch, we probably want
|
||||
// to stick to using that in exclusively backend code.
|
||||
stream: (archive.body as unknown) as Readable,
|
||||
subpath,
|
||||
etag: sha,
|
||||
filter: options?.filter,
|
||||
});
|
||||
}
|
||||
|
||||
private async doSearch(
|
||||
url: string,
|
||||
treesUrl: string,
|
||||
archiveUrl: string,
|
||||
sha: string,
|
||||
query: string,
|
||||
init: RequestInit,
|
||||
): Promise<SearchResponseFile[]> {
|
||||
function pathToUrl(path: string): string {
|
||||
// TODO(freben): Use the integration package facility for this instead
|
||||
// pathname starts as /backstage/backstage/blob/master/<path>
|
||||
const updated = new URL(url);
|
||||
const base = updated.pathname.split('/').slice(1, 5).join('/');
|
||||
updated.pathname = `${base}/${path}`;
|
||||
return updated.toString();
|
||||
}
|
||||
|
||||
const matcher = new Minimatch(query.replace(/^\/+/, ''));
|
||||
|
||||
// trees_url looks like "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}"
|
||||
const recursiveTree: GhTreeResponse = await this.fetchJson(
|
||||
treesUrl.replace('{/sha}', `/${sha}?recursive=true`),
|
||||
init,
|
||||
);
|
||||
|
||||
// The simple case is that we got the entire tree in a single operation.
|
||||
if (!recursiveTree.truncated) {
|
||||
const matching = recursiveTree.tree.filter(
|
||||
item =>
|
||||
item.type === 'blob' &&
|
||||
item.path &&
|
||||
item.url &&
|
||||
matcher.match(item.path),
|
||||
);
|
||||
|
||||
return matching.map(item => ({
|
||||
url: pathToUrl(item.path!),
|
||||
content: async () => {
|
||||
const blob: GhBlobResponse = await this.fetchJson(item.url!, init);
|
||||
return Buffer.from(blob.content, 'base64');
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// For larger repos, we leverage readTree and filter through that instead
|
||||
const tree = await this.doReadTree(archiveUrl, sha, '', init, {
|
||||
filter: path => matcher.match(path),
|
||||
});
|
||||
const files = await tree.files();
|
||||
|
||||
return files.map(file => ({
|
||||
url: pathToUrl(file.path),
|
||||
content: file.content,
|
||||
}));
|
||||
}
|
||||
|
||||
private async getRepoDetails(
|
||||
url: string,
|
||||
): Promise<{
|
||||
repo: GhRepoResponse;
|
||||
branch: GhBranchResponse;
|
||||
}> {
|
||||
const parsed = parseGitUrl(url);
|
||||
const { ref, full_name } = parsed;
|
||||
|
||||
// Caveat: The ref will totally be incorrect if the branch name includes a
|
||||
// slash. Thus, some operations can not work on URLs containing branch
|
||||
// names that have a slash in them.
|
||||
|
||||
const { headers } = await this.deps.credentialsProvider.getCredentials({
|
||||
url,
|
||||
});
|
||||
|
||||
const repo: GhRepoResponse = await this.fetchJson(
|
||||
`${this.config.apiBaseUrl}/repos/${full_name}`,
|
||||
{ headers },
|
||||
);
|
||||
|
||||
// branches_url looks like "https://api.github.com/repos/owner/repo/branches{/branch}"
|
||||
const branch: GhBranchResponse = await this.fetchJson(
|
||||
repo.branches_url.replace('{/branch}', `/${ref || repo.default_branch}`),
|
||||
{ headers },
|
||||
);
|
||||
|
||||
return { repo, branch };
|
||||
}
|
||||
|
||||
private async fetchResponse(
|
||||
url: string | URL,
|
||||
init: RequestInit,
|
||||
): Promise<Response> {
|
||||
const urlAsString = url.toString();
|
||||
|
||||
const response = await fetch(urlAsString, init);
|
||||
|
||||
if (!response.ok) {
|
||||
const message = `Request failed for ${urlAsString}, ${response.status} ${response.statusText}`;
|
||||
if (response.status === 404) {
|
||||
throw new NotFoundError(message);
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private async fetchJson(url: string | URL, init: RequestInit): Promise<any> {
|
||||
const response = await this.fetchResponse(url, init);
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,16 +21,17 @@ import {
|
||||
readGitLabIntegrationConfigs,
|
||||
} from '@backstage/integration';
|
||||
import fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '../errors';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import {
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
export class GitlabUrlReader implements UrlReader {
|
||||
private readonly treeResponseFactory: ReadTreeResponseFactory;
|
||||
@@ -154,6 +155,10 @@ export class GitlabUrlReader implements UrlReader {
|
||||
});
|
||||
}
|
||||
|
||||
async search(): Promise<SearchResponse> {
|
||||
throw new Error('GitlabUrlReader does not implement search');
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host, token } = this.config;
|
||||
return `gitlab{host=${host},authed=${Boolean(token)}}`;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user