Merge branch 'master' into fix-catalog-import
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@backstage/core-api': patch
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Add `BackstageRoutes` component to replace the top-level `Routes` component from `react-router` within apps, removing the need for manually appending `/*` to paths or sorting routes.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-register-component': patch
|
||||
---
|
||||
|
||||
Register component plugin is deprecated in favor of @backstage/plugin-catalog-import
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Add `CatalogIndexPage` and `CatalogEntityPage`, two new extensions that replace the existing `Router` component.
|
||||
|
||||
Add `EntityLayout` to replace `EntityPageLayout`, using children instead of an element property, and allowing for collection of all `RouteRef` mount points used within tabs.
|
||||
|
||||
Add `EntitySwitch` to be used to select components based on entity data, along with accompanying `isKind`, `isNamespace`, and `isComponentType` filters.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-api-docs': patch
|
||||
---
|
||||
|
||||
Link register API to catalog-import plugin
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/techdocs-common': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Moving the Git actions to isomorphic-git instead of the node binding version of nodegit
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Tweaked development log formatter to include extra fields at the end of each log line
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-fossa': patch
|
||||
---
|
||||
|
||||
Bump dependency versions of @backstage/core, cli and test-utils
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
fix bug in token expiration date
|
||||
@@ -134,6 +134,7 @@ neuro
|
||||
newrelic
|
||||
nginx
|
||||
Niklas
|
||||
nodegit
|
||||
nohoist
|
||||
nonces
|
||||
npm
|
||||
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
- uses: Klemensas/action-autotag@1.2.3
|
||||
if: matrix.node-version == '12.x'
|
||||
with:
|
||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
GITHUB_TOKEN: '${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}'
|
||||
package_root: 'packages/core'
|
||||
tag_prefix: 'v'
|
||||
|
||||
|
||||
+33
-21
@@ -6,19 +6,19 @@ Therefore we want to create strong community of contributors -- all working toge
|
||||
|
||||
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. ❤️
|
||||
|
||||
Backstage is released under the Apache2.0 License, and original creations contributed to this repo are accepted under the same license.
|
||||
Backstage is released under the Apache 2.0 License, and original creations contributed to this repo are accepted under the same license.
|
||||
|
||||
# Types of Contributions
|
||||
## Types of Contributions
|
||||
|
||||
## Report bugs
|
||||
### Report bugs
|
||||
|
||||
No one likes bugs. Report bugs as an issue [here](https://github.com/backstage/backstage/issues/new?template=bug_template.md).
|
||||
|
||||
## Fix bugs or build new features
|
||||
### Fix bugs or build new features
|
||||
|
||||
Look through the GitHub issues for [bugs](https://github.com/backstage/backstage/labels/bugs), [good first issues](https://github.com/backstage/backstage/labels/good%20first%20issue) or [help wanted](https://github.com/backstage/backstage/labels/help%20wanted).
|
||||
|
||||
## Build a plugin
|
||||
### Build a plugin
|
||||
|
||||
The value of Backstage grows with every new plugin that gets added. Wouldn't it be fantastic if there was a plugin for every infrastructure project out there? We think so. And we would love your help.
|
||||
|
||||
@@ -26,27 +26,27 @@ A great reference example of a plugin can be found on [our blog](https://backsta
|
||||
|
||||
What kind of plugins should/could be created? Some inspiration from the 120+ plugins that we have developed inside Spotify can be found [here](https://backstage.io/demos), but we will keep a running list of suggestions labeled with [[plugin]](https://github.com/backstage/backstage/labels/plugin).
|
||||
|
||||
## Suggesting a plugin
|
||||
### Suggesting a plugin
|
||||
|
||||
If you start developing a plugin that you aim to release as open source, we suggest that you create a [new Issue](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME). This helps the community know what plugins are in development.
|
||||
|
||||
You can also use this process if you have an idea for a good plugin but you hope that someone else will pick up the work.
|
||||
|
||||
## Adding Non-code Contributions
|
||||
### Adding Non-code Contributions
|
||||
|
||||
Since there is such a large landscape of possible development, build, and deployment environments, we welcome community contributions in these areas in the [`/contrib`](https://github.com/backstage/backstage/tree/master/contrib) folder of the project. This is an excellent place to put things that help out the community at large, but which may not fit within the scope of the core product to support natively. Here, you will find Helm charts, alternative Docker images, and much more.
|
||||
|
||||
## Write Documentation
|
||||
### Write Documentation
|
||||
|
||||
The current documentation is very limited. Help us make the `/docs` folder come alive.
|
||||
|
||||
## Contribute to Storybook
|
||||
### Contribute to Storybook
|
||||
|
||||
We think the best way to ensure different plugins provide a consistent experience is through a solid set of reusable UI/UX components. Backstage uses [Storybook](http://backstage.io/storybook).
|
||||
|
||||
Either help us [create new components](https://github.com/backstage/backstage/labels/help%20wanted) or improve stories for the existing ones (look for files with `*.stories.tsx`).
|
||||
|
||||
## Submit Feedback
|
||||
### Submit Feedback
|
||||
|
||||
The best way to send feedback is to file [an issue](https://github.com/backstage/backstage/issues).
|
||||
|
||||
@@ -58,47 +58,59 @@ If you are proposing a feature:
|
||||
- Remember that this is a volunteer-driven project, and that contributions
|
||||
are welcome :)
|
||||
|
||||
## Add your company to ADOPTERS
|
||||
### Add your company to ADOPTERS
|
||||
|
||||
Have you started using Backstage? Adding your company to [ADOPTERS](ADOPTERS.md) really helps the project.
|
||||
|
||||
# Get Started!
|
||||
## Get Started!
|
||||
|
||||
So...feel ready to jump in? Let's do this. 👏🏻💯
|
||||
|
||||
Start by reading our [Getting Started](https://backstage.io/docs/getting-started/) page. If you need help, just jump into our [Discord chatroom](https://discord.gg/MUpMjP2).
|
||||
|
||||
# Coding Guidelines
|
||||
## Coding Guidelines
|
||||
|
||||
All code is formatted with `prettier` using the configuration in the repo. If possible we recommend configuring your editor to format automatically, but you can also use the `yarn prettier --write <file>` command to format files.
|
||||
|
||||
A consistent coding style is included via [EditorConfig](https://editorconfig.org/) with the file [`.editorconfig`](.editorconfig) at the root of the repo. Depending on your editor of choice, it will either support it out of the box or you can [download a plugin](https://editorconfig.org/#download) for the config to be applied.
|
||||
|
||||
If you're contributing to the backend or CLI tooling, be mindful of cross-platform support. [This](https://shapeshed.com/writing-cross-platform-node/) blog post is a good guide of what to keep in mind when writing cross-platform NodeJS.
|
||||
|
||||
Also be sure to skim through our [ADRs](https://github.com/backstage/backstage/tree/master/docs/architecture-decisions) to see if they cover what you're working on. In particular [ADR006: Avoid React.FC and React.SFC](https://github.com/backstage/backstage/blob/master/docs/architecture-decisions/adr006-avoid-react-fc.md) is one to look out for.
|
||||
|
||||
If there are any updates in `markdown` file please make sure to run `yarn run lint:docs`. Though it is checked on `lint-staged`. It is required to install [vale](https://docs.errata.ai/vale/install) separately and make sure it is accessed by global command.
|
||||
|
||||
# Creating Changesets
|
||||
## Creating Changesets
|
||||
|
||||
We use [changesets](https://github.com/atlassian/changesets) to help us prepare releases. It helps us make sure that every package affected by a change gets a proper version number and an entry in its `CHANGELOG.md`. To make the process of generating releases easy. it helps when contributors include changesets with their pull requests.
|
||||
We use [changesets](https://github.com/atlassian/changesets) to help us prepare releases. They help us make sure that every package affected by a change gets a proper version number and an entry in its `CHANGELOG.md`. To make the process of generating releases easy, it helps when contributors include changesets with their pull requests.
|
||||
|
||||
## To create a changeset
|
||||
### When to use a changeset?
|
||||
|
||||
Any time a patch, minor, or major change aligning to [Semantic Versioning](https://semver.org) is made to any published package in `packages/` or `plugins/`, a changeset should be used. It helps to align your change to the [Backstage stability index](https://backstage.io/docs/overview/stability-index) for the package you are changing, for example, when to provide additional clarity on deprecation or impacting changes which will then be included into CHANGELOGs.
|
||||
|
||||
In general, changesets are not needed for the documentation, build utilities, contributed samples in `contrib/`, or the [example `packages/app`](packages/app).
|
||||
|
||||
### How to create a changeset
|
||||
|
||||
1. Run `yarn changeset`
|
||||
2. Select which packages you want to include a changeset for
|
||||
3. Select impact of change that you're introducing (minor, major or patch)
|
||||
3. Select impact of change that you're introducing (patch, minor, or major)
|
||||
4. Add generated changeset to Git
|
||||
5. Push the commit with your changeset to the branch associated with your PR
|
||||
6. Accept our gratitude for making the release process easier on the maintainer
|
||||
6. Accept our gratitude for making the release process easier on the maintainers
|
||||
|
||||
For more information, checkout [adding a changeset](https://github.com/atlassian/changesets/blob/master/docs/adding-a-changeset.md) documentation in changesets repository.
|
||||
For more information, checkout [adding a changeset](https://github.com/atlassian/changesets/blob/master/docs/adding-a-changeset.md) documentation in the changesets repository.
|
||||
|
||||
# Code of Conduct
|
||||
## Merging to Master
|
||||
|
||||
For those contributors who have earned write access to the repository, when a pull request is approved, in general we prefer the author of the PR to perform the merge themselves. This allows them to own accountability for the change and they likely know best how or when to address pending fixes or additional follow-ups. In this way, we all help contribute to the project's successful outcomes.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project adheres to the [Spotify FOSS Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
|
||||
|
||||
[code-of-conduct]: https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md
|
||||
|
||||
# Security Issues?
|
||||
## Security Issues?
|
||||
|
||||
See [SECURITY](SECURITY.md).
|
||||
|
||||
+10
-14
@@ -17,22 +17,18 @@ const Background = props => {
|
||||
<Block small className="stripe-bottom bg-black-grey">
|
||||
<Block.Container style={{ justifyContent: 'flex-start' }}>
|
||||
<Block.TextBox>
|
||||
<Block.Title>See us in action.</Block.Title>
|
||||
<Block.Title>See us in action</Block.Title>
|
||||
<Block.Paragraph>
|
||||
To illustrate the potential of Backstage, we’re showing you{' '}
|
||||
<a href="https://labs.spotify.com/2020/04/21/how-we-use-backstage-at-spotify/">
|
||||
how we use it here at Spotify
|
||||
To explore the UI and basic features of Backstage firsthand, go
|
||||
to: <a href="https://demo.backstage.io">demo.backstage.io</a>.
|
||||
</Block.Paragraph>
|
||||
<Block.Paragraph>
|
||||
Watch the videos below to get an introduction to Backstage and to
|
||||
see how we use different plugins to customize{' '}
|
||||
<a href="https://engineering.atspotify.com/2020/04/21/how-we-use-backstage-at-spotify/">
|
||||
our internal version of Backstage at Spotify
|
||||
</a>
|
||||
. The videos below feature the custom plugins and templates that
|
||||
we built for our internal version of Backstage. You can use
|
||||
Backstage to build the developer portal you want — integrating the
|
||||
tools that you use in your own infrastructure ecosystem. (Or get
|
||||
started by building an open source plugin for Backstage that
|
||||
everyone can use, like our{' '}
|
||||
<a href="https://backstage.io/blog/2020/04/06/lighthouse-plugin">
|
||||
Lighthouse Plugin
|
||||
</a>
|
||||
.)
|
||||
.
|
||||
</Block.Paragraph>
|
||||
</Block.TextBox>
|
||||
<Block.Graphics>
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"fs-extra": "^9.0.1",
|
||||
"git-url-parse": "^11.4.3",
|
||||
"helmet": "^4.0.0",
|
||||
"isomorphic-git": "^1.8.0",
|
||||
"knex": "^0.21.6",
|
||||
"lodash": "^4.17.15",
|
||||
"logform": "^2.1.1",
|
||||
|
||||
@@ -24,3 +24,4 @@ export * from './reading';
|
||||
export * from './service';
|
||||
export * from './paths';
|
||||
export * from './hot';
|
||||
export * from './scm';
|
||||
|
||||
@@ -17,19 +17,23 @@ import * as winston from 'winston';
|
||||
import { TransformableInfo } from 'logform';
|
||||
|
||||
const coloredTemplate = (info: TransformableInfo) => {
|
||||
const { timestamp, level, message, plugin, service } = info;
|
||||
const { timestamp, level, message, plugin, service, ...fields } = info;
|
||||
const colorizer = winston.format.colorize();
|
||||
const prefix = plugin || service;
|
||||
const timestampColor = colorizer.colorize('timestamp', timestamp);
|
||||
const prefixColor = colorizer.colorize('prefix', prefix);
|
||||
|
||||
return `${timestampColor} ${prefixColor} ${level} ${message}`;
|
||||
const extraFields = Object.entries(fields)
|
||||
.map(([key, value]) => `${colorizer.colorize('field', `${key}`)}=${value}`)
|
||||
.join(' ');
|
||||
|
||||
return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`;
|
||||
};
|
||||
|
||||
export const coloredFormat = winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.colorize({
|
||||
colors: { timestamp: 'dim', prefix: 'blue' },
|
||||
colors: { timestamp: 'dim', prefix: 'blue', field: 'cyan' },
|
||||
}),
|
||||
winston.format.printf(coloredTemplate),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
jest.mock('isomorphic-git');
|
||||
jest.mock('isomorphic-git/http/node');
|
||||
jest.mock('fs-extra');
|
||||
|
||||
import * as isomorphic from 'isomorphic-git';
|
||||
import { Git } from './git';
|
||||
import http from 'isomorphic-git/http/node';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
describe('Git', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
describe('add', () => {
|
||||
it('should call isomorphic-git add with the correct arguments', async () => {
|
||||
const git = Git.fromAuth({});
|
||||
const dir = 'mockdirectory';
|
||||
const filepath = 'mockfile/path';
|
||||
|
||||
await git.add({ dir, filepath });
|
||||
|
||||
expect(isomorphic.add).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
filepath,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addRemote', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const git = Git.fromAuth({});
|
||||
const dir = 'mockdirectory';
|
||||
const remote = 'origin';
|
||||
const url = 'git@github.com/something/sads';
|
||||
|
||||
await git.addRemote({ dir, remote, url });
|
||||
|
||||
expect(isomorphic.addRemote).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
remote,
|
||||
url,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('commit', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const git = Git.fromAuth({});
|
||||
const dir = 'mockdirectory';
|
||||
const message = 'Inital Commit';
|
||||
const author = {
|
||||
name: 'author',
|
||||
email: 'test@backstage.io',
|
||||
};
|
||||
const committer = {
|
||||
name: 'comitter',
|
||||
email: 'test@backstage.io',
|
||||
};
|
||||
|
||||
await git.commit({ dir, message, author, committer });
|
||||
|
||||
expect(isomorphic.commit).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
message,
|
||||
author,
|
||||
committer,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clone', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const url = 'http://github.com/some/repo';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.clone({ url, dir });
|
||||
|
||||
expect(isomorphic.clone).toHaveBeenCalledWith({
|
||||
fs,
|
||||
http,
|
||||
url,
|
||||
dir,
|
||||
singleBranch: true,
|
||||
depth: 1,
|
||||
onProgress: expect.any(Function),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
onAuth: expect.any(Function),
|
||||
});
|
||||
});
|
||||
it('should pass a function that returns the authorization as the onAuth handler', async () => {
|
||||
const url = 'http://github.com/some/repo';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.clone({ url, dir });
|
||||
|
||||
const { onAuth } = ((isomorphic.clone as unknown) as jest.Mock<
|
||||
typeof isomorphic['clone']
|
||||
>).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
});
|
||||
});
|
||||
|
||||
describe('currentBranch', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
const fullName = true;
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.currentBranch({ dir, fullName });
|
||||
|
||||
expect(isomorphic.currentBranch).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
fullname: true,
|
||||
});
|
||||
|
||||
await git.currentBranch({ dir });
|
||||
|
||||
expect(isomorphic.currentBranch).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
fullname: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetch', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const remote = 'http://github.com/some/repo';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.fetch({ remote, dir });
|
||||
|
||||
expect(isomorphic.fetch).toHaveBeenCalledWith({
|
||||
fs,
|
||||
http,
|
||||
remote,
|
||||
dir,
|
||||
onProgress: expect.any(Function),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
onAuth: expect.any(Function),
|
||||
});
|
||||
});
|
||||
it('should pass a function that returns the authorization as the onAuth handler', async () => {
|
||||
const remote = 'http://github.com/some/repo';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.fetch({ remote, dir });
|
||||
|
||||
const { onAuth } = ((isomorphic.fetch as unknown) as jest.Mock<
|
||||
typeof isomorphic['fetch']
|
||||
>).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
});
|
||||
});
|
||||
|
||||
describe('init', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.init({ dir });
|
||||
|
||||
expect(isomorphic.init).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('merge', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
const author = {
|
||||
name: 'author',
|
||||
email: 'test@backstage.io',
|
||||
};
|
||||
const committer = {
|
||||
name: 'comitter',
|
||||
email: 'test@backstage.io',
|
||||
};
|
||||
const theirs = 'master';
|
||||
const ours = 'production';
|
||||
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.merge({ dir, theirs, ours, author, committer });
|
||||
|
||||
expect(isomorphic.merge).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
ours,
|
||||
theirs,
|
||||
author,
|
||||
committer,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('push', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const remote = 'origin';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.push({ dir, remote });
|
||||
|
||||
expect(isomorphic.push).toHaveBeenCalledWith({
|
||||
fs,
|
||||
http,
|
||||
remote,
|
||||
dir,
|
||||
onProgress: expect.any(Function),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
onAuth: expect.any(Function),
|
||||
});
|
||||
});
|
||||
it('should pass a function that returns the authorization as the onAuth handler', async () => {
|
||||
const remote = 'origin';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.push({ remote, dir });
|
||||
|
||||
const { onAuth } = ((isomorphic.push as unknown) as jest.Mock<
|
||||
typeof isomorphic['push']
|
||||
>).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readCommit', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
const sha = 'as43bd7';
|
||||
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.readCommit({ dir, sha });
|
||||
|
||||
expect(isomorphic.readCommit).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
oid: sha,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRef', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
const ref = 'as43bd7';
|
||||
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.resolveRef({ dir, ref });
|
||||
|
||||
expect(isomorphic.resolveRef).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
ref,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import git, {
|
||||
ProgressCallback,
|
||||
MergeResult,
|
||||
ReadCommitResult,
|
||||
} from 'isomorphic-git';
|
||||
import http from 'isomorphic-git/http/node';
|
||||
import fs from 'fs-extra';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
/*
|
||||
provider username password
|
||||
GitHub token 'x-oauth-basic'
|
||||
GitHub App token 'x-access-token'
|
||||
BitBucket 'x-token-auth' token
|
||||
GitLab 'oauth2' token
|
||||
From : https://isomorphic-git.org/docs/en/onAuth
|
||||
|
||||
Azure 'notempty' token
|
||||
*/
|
||||
export class Git {
|
||||
private constructor(
|
||||
private readonly config: {
|
||||
username?: string;
|
||||
password?: string;
|
||||
logger?: Logger;
|
||||
},
|
||||
) {}
|
||||
|
||||
async add({
|
||||
dir,
|
||||
filepath,
|
||||
}: {
|
||||
dir: string;
|
||||
filepath: string;
|
||||
}): Promise<void> {
|
||||
this.config.logger?.info(`Adding file {dir=${dir},filepath=${filepath}}`);
|
||||
|
||||
return git.add({ fs, dir, filepath });
|
||||
}
|
||||
|
||||
async addRemote({
|
||||
dir,
|
||||
url,
|
||||
remote,
|
||||
}: {
|
||||
dir: string;
|
||||
remote: string;
|
||||
url: string;
|
||||
}): Promise<void> {
|
||||
this.config.logger?.info(
|
||||
`Creating new remote {dir=${dir},remote=${remote},url=${url}}`,
|
||||
);
|
||||
return git.addRemote({ fs, dir, remote, url });
|
||||
}
|
||||
|
||||
async commit({
|
||||
dir,
|
||||
message,
|
||||
author,
|
||||
committer,
|
||||
}: {
|
||||
dir: string;
|
||||
message: string;
|
||||
author: { name: string; email: string };
|
||||
committer: { name: string; email: string };
|
||||
}): Promise<string> {
|
||||
this.config.logger?.info(
|
||||
`Committing file to repo {dir=${dir},message=${message}}`,
|
||||
);
|
||||
|
||||
return git.commit({ fs, dir, message, author, committer });
|
||||
}
|
||||
|
||||
async clone({ url, dir }: { url: string; dir: string }): Promise<void> {
|
||||
this.config.logger?.info(`Cloning repo {dir=${dir},url=${url}}`);
|
||||
return git.clone({
|
||||
fs,
|
||||
http,
|
||||
url,
|
||||
dir,
|
||||
singleBranch: true,
|
||||
depth: 1,
|
||||
onProgress: this.onProgressHandler(),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
onAuth: this.onAuth,
|
||||
});
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/currentBranch
|
||||
async currentBranch({
|
||||
dir,
|
||||
fullName,
|
||||
}: {
|
||||
dir: string;
|
||||
fullName?: boolean;
|
||||
}): Promise<string | undefined> {
|
||||
const fullname = fullName ?? false;
|
||||
return git.currentBranch({ fs, dir, fullname }) as Promise<
|
||||
string | undefined
|
||||
>;
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/fetch
|
||||
async fetch({
|
||||
dir,
|
||||
remote,
|
||||
}: {
|
||||
dir: string;
|
||||
remote?: string;
|
||||
}): Promise<void> {
|
||||
const remoteValue = remote ?? 'origin';
|
||||
this.config.logger?.info(
|
||||
`Fetching remote=${remoteValue} for repository {dir=${dir}}`,
|
||||
);
|
||||
await git.fetch({
|
||||
fs,
|
||||
http,
|
||||
dir,
|
||||
remote: remoteValue,
|
||||
onProgress: this.onProgressHandler(),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
onAuth: this.onAuth,
|
||||
});
|
||||
}
|
||||
|
||||
async init({ dir }: { dir: string }): Promise<void> {
|
||||
this.config.logger?.info(`Init git repository {dir=${dir}}`);
|
||||
|
||||
return git.init({
|
||||
fs,
|
||||
dir,
|
||||
});
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/merge
|
||||
async merge({
|
||||
dir,
|
||||
theirs,
|
||||
ours,
|
||||
author,
|
||||
committer,
|
||||
}: {
|
||||
dir: string;
|
||||
theirs: string;
|
||||
ours?: string;
|
||||
author: { name: string; email: string };
|
||||
committer: { name: string; email: string };
|
||||
}): Promise<MergeResult> {
|
||||
this.config.logger?.info(
|
||||
`Merging branch '${theirs}' into '${ours}' for repository {dir=${dir}}`,
|
||||
);
|
||||
|
||||
// If ours is undefined, current branch is used.
|
||||
return git.merge({
|
||||
fs,
|
||||
dir,
|
||||
ours,
|
||||
theirs,
|
||||
author,
|
||||
committer,
|
||||
});
|
||||
}
|
||||
|
||||
async push({ dir, remote }: { dir: string; remote: string }) {
|
||||
this.config.logger?.info(
|
||||
`Pushing directory to remote {dir=${dir},remote=${remote}}`,
|
||||
);
|
||||
return git.push({
|
||||
fs,
|
||||
dir,
|
||||
http,
|
||||
onProgress: this.onProgressHandler(),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
remote: remote,
|
||||
onAuth: this.onAuth,
|
||||
});
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/readCommit
|
||||
async readCommit({
|
||||
dir,
|
||||
sha,
|
||||
}: {
|
||||
dir: string;
|
||||
sha: string;
|
||||
}): Promise<ReadCommitResult> {
|
||||
return git.readCommit({ fs, dir, oid: sha });
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/resolveRef
|
||||
async resolveRef({
|
||||
dir,
|
||||
ref,
|
||||
}: {
|
||||
dir: string;
|
||||
ref: string;
|
||||
}): Promise<string> {
|
||||
return git.resolveRef({ fs, dir, ref });
|
||||
}
|
||||
|
||||
private onAuth = () => ({
|
||||
username: this.config.username,
|
||||
password: this.config.password,
|
||||
});
|
||||
|
||||
private onProgressHandler = (): ProgressCallback => {
|
||||
let currentPhase = '';
|
||||
|
||||
return event => {
|
||||
if (currentPhase !== event.phase) {
|
||||
currentPhase = event.phase;
|
||||
this.config.logger?.info(event.phase);
|
||||
}
|
||||
const total = event.total
|
||||
? `${Math.round((event.loaded / event.total) * 100)}%`
|
||||
: event.loaded;
|
||||
this.config.logger?.debug(`status={${event.phase},total={${total}}}`);
|
||||
};
|
||||
};
|
||||
|
||||
static fromAuth = ({
|
||||
username,
|
||||
password,
|
||||
logger,
|
||||
}: {
|
||||
username?: string;
|
||||
password?: string;
|
||||
logger?: Logger;
|
||||
}) => new Git({ username, password, logger });
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Git } from './git';
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d45efbc9b: Fix typo in .app.listen.port config schema
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.4.3",
|
||||
"version": "0.4.4",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -113,8 +113,8 @@
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.4.1",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@types/diff": "^4.0.2",
|
||||
@@ -182,7 +182,7 @@
|
||||
"visibility": "frontend",
|
||||
"description": "The host that the frontend should be bound to. Only used for local development."
|
||||
},
|
||||
"post": {
|
||||
"port": {
|
||||
"type": "number",
|
||||
"visibility": "frontend",
|
||||
"description": "The port that the frontend should be bound to. Only used for local development."
|
||||
|
||||
@@ -14,20 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin';
|
||||
|
||||
describe('LinkedPackageResolvePlugin', () => {
|
||||
const root = os.platform() === 'win32' ? 'C:\\root' : '/root';
|
||||
|
||||
it('should re-write paths for external packages', () => {
|
||||
const plugin = new LinkedPackageResolvePlugin('/root/repo/node_modules', [
|
||||
{
|
||||
name: 'a',
|
||||
location: '/root/external-a',
|
||||
},
|
||||
{
|
||||
name: '@s/b',
|
||||
location: '/root/external-b',
|
||||
},
|
||||
]);
|
||||
const plugin = new LinkedPackageResolvePlugin(
|
||||
path.resolve(root, 'repo/node_modules'),
|
||||
[
|
||||
{
|
||||
name: 'a',
|
||||
location: path.resolve(root, 'external-a'),
|
||||
},
|
||||
{
|
||||
name: '@s/b',
|
||||
location: path.resolve(root, 'external-b'),
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const tapAsync = jest.fn();
|
||||
const doResolve = jest.fn();
|
||||
@@ -50,10 +57,10 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
const callbackX = jest.fn();
|
||||
tap(
|
||||
{
|
||||
request: '/root/repo/package/x/src/module.ts',
|
||||
path: '/root/repo/package/x/src',
|
||||
request: path.resolve(root, 'repo/package/x/src/module.ts'),
|
||||
path: path.resolve(root, 'repo/package/x/src'),
|
||||
context: {
|
||||
issuer: '/root/repo/package/x/src/index.ts',
|
||||
issuer: path.resolve(root, 'repo/package/x/src/index.ts'),
|
||||
},
|
||||
},
|
||||
'some-context',
|
||||
@@ -81,10 +88,10 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
const callbackA = jest.fn();
|
||||
tap(
|
||||
{
|
||||
request: '/root/external-a/src/module.ts',
|
||||
path: '/root/external-a/src',
|
||||
request: path.resolve(root, 'external-a/src/module.ts'),
|
||||
path: path.resolve(root, 'external-a/src'),
|
||||
context: {
|
||||
issuer: '/root/external-a/src/index.ts',
|
||||
issuer: path.resolve(root, 'external-a/src/index.ts'),
|
||||
},
|
||||
},
|
||||
'some-context',
|
||||
@@ -95,13 +102,16 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
expect(doResolve).toHaveBeenCalledWith(
|
||||
resolver.hooks.resolve,
|
||||
{
|
||||
request: '/root/external-a/src/module.ts',
|
||||
path: '/root/repo/node_modules/a/src',
|
||||
request: path.resolve(root, 'external-a/src/module.ts'),
|
||||
path: path.resolve(root, 'repo/node_modules/a/src'),
|
||||
context: {
|
||||
issuer: '/root/repo/node_modules/a/src/index.ts',
|
||||
issuer: path.resolve(root, 'repo/node_modules/a/src/index.ts'),
|
||||
},
|
||||
},
|
||||
'resolve /root/external-a/src/module.ts in /root/repo/node_modules/a',
|
||||
`resolve ${path.resolve(
|
||||
root,
|
||||
'external-a/src/module.ts',
|
||||
)} in ${path.resolve(root, 'repo/node_modules/a')}`,
|
||||
'some-context',
|
||||
callbackA,
|
||||
);
|
||||
@@ -110,8 +120,8 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
const callbackB = jest.fn();
|
||||
tap(
|
||||
{
|
||||
request: '/root/external-b/src/module.ts',
|
||||
path: '/root/external-b/src',
|
||||
request: path.resolve(root, 'external-b/src/module.ts'),
|
||||
path: path.resolve(root, 'external-b/src'),
|
||||
context: {
|
||||
issuer: false,
|
||||
},
|
||||
@@ -124,13 +134,16 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
expect(doResolve).toHaveBeenLastCalledWith(
|
||||
resolver.hooks.resolve,
|
||||
{
|
||||
request: '/root/external-b/src/module.ts',
|
||||
path: '/root/repo/node_modules/@s/b/src',
|
||||
request: path.resolve(root, 'external-b/src/module.ts'),
|
||||
path: path.resolve(root, 'repo/node_modules/@s/b/src'),
|
||||
context: {
|
||||
issuer: false,
|
||||
},
|
||||
},
|
||||
'resolve /root/external-b/src/module.ts in /root/repo/node_modules/@s/b',
|
||||
`resolve ${path.resolve(
|
||||
root,
|
||||
'external-b/src/module.ts',
|
||||
)} in ${path.resolve(root, 'repo/node_modules/@s/b')}`,
|
||||
'some-context',
|
||||
callbackB,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/core-api
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a08c32ced: Add `FlatRoutes` component to replace the top-level `Routes` component from `react-router` within apps, removing the need for manually appending `/*` to paths or sorting routes.
|
||||
- 86c3c652a: Deprecate `RouteRef` path parameter and member, and remove deprecated `routeRef.createSubRouteRef`.
|
||||
- 27f2af935: Delay auth loginPopup close to avoid race condition with callers of authFlowHelpers.
|
||||
|
||||
## 0.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-api",
|
||||
"description": "Internal Core API used by Backstage plugins and apps",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -43,7 +43,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/test-utils-core": "^0.1.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
+2
-4
@@ -66,13 +66,11 @@ function createRoutesFromChildren(children: ReactNode): RouteObject[] {
|
||||
});
|
||||
}
|
||||
|
||||
type BackstageRoutesProps = {
|
||||
type FlatRoutesProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const BackstageRoutes = (
|
||||
props: BackstageRoutesProps,
|
||||
): JSX.Element | null => {
|
||||
export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => {
|
||||
const routes = createRoutesFromChildren(props.children);
|
||||
return useRoutes(routes);
|
||||
};
|
||||
@@ -25,25 +25,15 @@ export class AbsoluteRouteRef<Params extends { [param in string]: string }> {
|
||||
|
||||
// TODO(Rugvip): Remove this, routes are looked up via the registry instead
|
||||
get path() {
|
||||
return this.config.path;
|
||||
return this.config.path ?? '';
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.config.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should not be used, create a separate RouteRef instead
|
||||
* @deprecated
|
||||
*/
|
||||
createSubRoute(): any {
|
||||
throw new Error(
|
||||
'This method should not be called, create a separate RouteRef instead',
|
||||
);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `routeRef{path=${this.path}}`;
|
||||
return `routeRef{title=${this.title}}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,13 +44,7 @@ export function createRouteRef<
|
||||
return new AbsoluteRouteRef<Params>(config);
|
||||
}
|
||||
|
||||
const create = Symbol('create-external-route-ref');
|
||||
|
||||
export class ExternalRouteRef {
|
||||
static [create]() {
|
||||
return new ExternalRouteRef();
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
toString() {
|
||||
@@ -69,5 +53,5 @@ export class ExternalRouteRef {
|
||||
}
|
||||
|
||||
export function createExternalRouteRef(): ExternalRouteRef {
|
||||
return ExternalRouteRef[create]();
|
||||
return new ((ExternalRouteRef as unknown) as { new (): ExternalRouteRef })();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ export type {
|
||||
ConcreteRoute,
|
||||
MutableRouteRef,
|
||||
} from './types';
|
||||
export { BackstageRoutes } from './BackstageRoutes';
|
||||
export { FlatRoutes } from './FlatRoutes';
|
||||
export { createRouteRef } from './RouteRef';
|
||||
export { useRouteRef } from './hooks';
|
||||
|
||||
@@ -19,14 +19,10 @@ import { IconComponent } from '../icons';
|
||||
// @ts-ignore, we're just embedding the Params type for usage in other places
|
||||
export type RouteRef<Params extends { [param in string]: string } = {}> = {
|
||||
// TODO(Rugvip): Remove path, look up via registry instead
|
||||
/** @deprecated paths are no longer accessed directly from RouteRefs, use useRouteRef instead */
|
||||
path: string;
|
||||
icon?: IconComponent;
|
||||
title: string;
|
||||
/**
|
||||
* This function should not be used, create a separate RouteRef instead
|
||||
* @deprecated
|
||||
*/
|
||||
createSubRoute(): any;
|
||||
};
|
||||
|
||||
export type AnyRouteRef = RouteRef<any>;
|
||||
@@ -51,7 +47,8 @@ export type MutableRouteRef = RouteRef<{}>;
|
||||
|
||||
export type RouteRefConfig<Params extends { [param in string]: string }> = {
|
||||
params?: Array<keyof Params>;
|
||||
path: string;
|
||||
/** @deprecated Route refs no longer decide their own path */
|
||||
path?: string;
|
||||
icon?: IconComponent;
|
||||
title: string;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/core
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a08c32ced: Add `FlatRoutes` component to replace the top-level `Routes` component from `react-router` within apps, removing the need for manually appending `/*` to paths or sorting routes.
|
||||
- Updated dependencies [a08c32ced]
|
||||
- Updated dependencies [86c3c652a]
|
||||
- Updated dependencies [27f2af935]
|
||||
- @backstage/core-api@0.2.8
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core",
|
||||
"description": "Core API used by Backstage plugins and apps",
|
||||
"version": "0.4.2",
|
||||
"version": "0.4.3",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core-api": "^0.2.7",
|
||||
"@backstage/core-api": "^0.2.8",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -65,7 +65,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# @backstage/create-app
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4e0e3b1bf: Add missing `yarn clean` for app.
|
||||
|
||||
For users with existing Backstage installations, add the following under the `scripts` section in `packages/app/package.json`, after the "lint" entry:
|
||||
|
||||
```json
|
||||
"clean": "backstage-cli clean",
|
||||
```
|
||||
|
||||
This will add the missing `yarn clean` for the generated frontend.
|
||||
|
||||
- 352a6581f: Added `"start-backend"` script to root `package.json`.
|
||||
|
||||
To apply this change to an existing app, add the following script to the root `package.json`:
|
||||
|
||||
```json
|
||||
"start-backend": "yarn workspace backend start"
|
||||
```
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -24,6 +24,8 @@ const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src'));
|
||||
if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) {
|
||||
require('..');
|
||||
} else {
|
||||
// Only used for development, so should be a devDependency
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
require('ts-node').register({
|
||||
transpileOnly: true,
|
||||
project: path.resolve(__dirname, '../../../tsconfig.json'),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/create-app",
|
||||
"description": "Create app package for Backstage",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -39,13 +39,13 @@
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.4.1",
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/plugin-api-docs": "^0.4.1",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-api-docs": "^0.4.2",
|
||||
"@backstage/plugin-app-backend": "^0.3.3",
|
||||
"@backstage/plugin-auth-backend": "^0.2.7",
|
||||
"@backstage/plugin-catalog": "^0.2.8",
|
||||
"@backstage/plugin-auth-backend": "^0.2.8",
|
||||
"@backstage/plugin-catalog": "^0.2.9",
|
||||
"@backstage/plugin-catalog-backend": "^0.5.1",
|
||||
"@backstage/plugin-catalog-import": "^0.3.2",
|
||||
"@backstage/plugin-circleci": "^0.2.5",
|
||||
@@ -58,8 +58,8 @@
|
||||
"@backstage/plugin-search": "^0.2.4",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.3.6",
|
||||
"@backstage/plugin-tech-radar": "^0.3.1",
|
||||
"@backstage/plugin-techdocs": "^0.5.1",
|
||||
"@backstage/plugin-techdocs-backend": "^0.5.0",
|
||||
"@backstage/plugin-techdocs": "^0.5.2",
|
||||
"@backstage/plugin-techdocs-backend": "^0.5.1",
|
||||
"@backstage/plugin-user-settings": "^0.2.3",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
@@ -70,6 +70,11 @@
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
"ts-node": "^8.6.2"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"watch": "./src",
|
||||
"exec": "bin/backstage-create-app",
|
||||
"ext": "ts"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"dist",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "yarn workspace app start",
|
||||
"start-backend": "yarn workspace backend start",
|
||||
"build": "lerna run build",
|
||||
"build-image": "yarn workspace backend build-image",
|
||||
"tsc": "tsc",
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"build": "backstage-cli app:build",
|
||||
"test": "backstage-cli test",
|
||||
"lint": "backstage-cli lint",
|
||||
"clean": "backstage-cli clean",
|
||||
"test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev",
|
||||
"test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run",
|
||||
"cy:dev": "cypress open",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/dev-utils
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 696b8ce74: Add new `addPage` method for use with extensions, as well as an `EntityGridItem` to easily create different test cases for entity overview cards.
|
||||
- Updated dependencies [a08c32ced]
|
||||
- Updated dependencies [7e0b8cac5]
|
||||
- Updated dependencies [87c0c53c2]
|
||||
- @backstage/core@0.4.3
|
||||
- @backstage/plugin-catalog@0.2.9
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/dev-utils",
|
||||
"description": "Utilities for developing Backstage plugins.",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.7",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,7 +29,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.0",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/plugin-catalog": "^0.2.9",
|
||||
"@backstage/test-utils": "^0.1.5",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -45,7 +47,7 @@
|
||||
"react-router-dom": "6.0.0-beta.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.1",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^12.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Grid, GridProps, makeStyles } from '@material-ui/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme, { entity: Entity }>(theme => ({
|
||||
root: ({ entity }) => ({
|
||||
position: 'relative',
|
||||
|
||||
'&::before': {
|
||||
content: `"${entity.metadata.name}"`,
|
||||
top: -theme.typography.fontSize + 4,
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
color: theme.palette.textSubtle,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
export const EntityGridItem = ({
|
||||
entity,
|
||||
classes,
|
||||
...rest
|
||||
}: Omit<GridProps, 'item' | 'container'> & { entity: Entity }): JSX.Element => {
|
||||
const itemClasses = useStyles({ entity });
|
||||
|
||||
return (
|
||||
<EntityProvider entity={entity}>
|
||||
<Grid classes={{ root: itemClasses.root, ...classes }} {...rest} item />
|
||||
</EntityProvider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { EntityGridItem } from './EntityGridItem';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './EntityGridItem';
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import React, { ComponentType, ReactNode } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import BookmarkIcon from '@material-ui/icons/Bookmark';
|
||||
@@ -29,9 +29,25 @@ import {
|
||||
AlertDisplay,
|
||||
OAuthRequestDialog,
|
||||
AnyApiFactory,
|
||||
IconComponent,
|
||||
FlatRoutes,
|
||||
attachComponentData,
|
||||
} from '@backstage/core';
|
||||
import SentimentDissatisfiedIcon from '@material-ui/icons/SentimentDissatisfied';
|
||||
import { Routes } from 'react-router';
|
||||
import { Outlet } from 'react-router';
|
||||
|
||||
const GatheringRoute: (props: {
|
||||
path: string;
|
||||
children: JSX.Element;
|
||||
}) => JSX.Element = () => <Outlet />;
|
||||
|
||||
attachComponentData(GatheringRoute, 'core.gatherMountPoints', true);
|
||||
|
||||
type RegisterPageOptions = {
|
||||
element: JSX.Element;
|
||||
title?: string;
|
||||
icon?: IconComponent;
|
||||
};
|
||||
|
||||
// TODO(rugvip): export proper plugin type from core that isn't the plugin class
|
||||
type BackstagePlugin = ReturnType<typeof createPlugin>;
|
||||
@@ -44,6 +60,8 @@ class DevAppBuilder {
|
||||
private readonly plugins = new Array<BackstagePlugin>();
|
||||
private readonly apis = new Array<AnyApiFactory>();
|
||||
private readonly rootChildren = new Array<ReactNode>();
|
||||
private readonly routes = new Array<JSX.Element>();
|
||||
private readonly sidebarItems = new Array<JSX.Element>();
|
||||
|
||||
/**
|
||||
* Register one or more plugins to render in the dev app
|
||||
@@ -75,6 +93,21 @@ class DevAppBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
addPage({ element, title, icon }: RegisterPageOptions): DevAppBuilder {
|
||||
const path = `/page-${this.routes.length + 1}`;
|
||||
this.sidebarItems.push(
|
||||
<SidebarItem
|
||||
key={path}
|
||||
to={path}
|
||||
text={title ?? path}
|
||||
icon={icon ?? BookmarkIcon}
|
||||
/>,
|
||||
);
|
||||
this.routes.push(
|
||||
<GatheringRoute key={path} path={path} children={element} />,
|
||||
);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Build a DevApp component using the resources registered so far
|
||||
*/
|
||||
@@ -100,7 +133,10 @@ class DevAppBuilder {
|
||||
<AppRouter>
|
||||
<SidebarPage>
|
||||
{sidebar}
|
||||
<Routes>{deprecatedAppRoutes}</Routes>
|
||||
<FlatRoutes>
|
||||
{this.routes}
|
||||
{deprecatedAppRoutes}
|
||||
</FlatRoutes>
|
||||
</SidebarPage>
|
||||
</AppRouter>
|
||||
</AppProvider>
|
||||
@@ -114,7 +150,12 @@ class DevAppBuilder {
|
||||
* Build and render directory to #root element, with react hot loading.
|
||||
*/
|
||||
render(): void {
|
||||
const DevApp = hot(this.build());
|
||||
const hotModule =
|
||||
require.cache['./dev/index.tsx'] ??
|
||||
require.cache['./dev/index.ts'] ??
|
||||
module;
|
||||
|
||||
const DevApp = hot(hotModule)(this.build());
|
||||
|
||||
const paths = this.findPluginPaths(this.plugins);
|
||||
|
||||
@@ -166,6 +207,7 @@ class DevAppBuilder {
|
||||
return (
|
||||
<Sidebar>
|
||||
<SidebarSpacer />
|
||||
{this.sidebarItems}
|
||||
{sidebarItems}
|
||||
</Sidebar>
|
||||
);
|
||||
@@ -199,7 +241,7 @@ class DevAppBuilder {
|
||||
// this to provide their own plugin dev wrappers.
|
||||
|
||||
/**
|
||||
* Creates a dev app for rendering one or more plugins and exposing the touchpoints of the plugin.
|
||||
* Creates a dev app for rendering one or more plugins and exposing the touch points of the plugin.
|
||||
*/
|
||||
export function createDevApp() {
|
||||
return new DevAppBuilder();
|
||||
|
||||
@@ -13,4 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './components';
|
||||
export * from './devApp';
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @backstage/techdocs-common
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8804e8981: Using @backstage/integration package for GitHub/GitLab/Azure tokens and request options.
|
||||
|
||||
Most probably you do not have to make any changes in the app because of this change.
|
||||
However, if you are using the `DirectoryPreparer` or `CommonGitPreparer` exported by
|
||||
`@backstage/techdocs-common` package, you now need to add pass in a `config` (from `@backstage/config`)
|
||||
instance as argument.
|
||||
|
||||
```
|
||||
<!-- Before -->
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const commonGitPreparer = new CommonGitPreparer(logger);
|
||||
<!-- Now -->
|
||||
const directoryPreparer = new DirectoryPreparer(config, logger);
|
||||
const commonGitPreparer = new CommonGitPreparer(config, logger);
|
||||
```
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/techdocs-common",
|
||||
"description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -39,6 +39,7 @@
|
||||
"@backstage/backend-common": "^0.4.1",
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/integration": "^0.1.4",
|
||||
"@google-cloud/storage": "^5.6.0",
|
||||
"@types/dockerode": "^3.2.1",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -50,18 +51,16 @@
|
||||
"js-yaml": "^3.14.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"mock-fs": "^4.13.0",
|
||||
"nodegit": "^0.27.0",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@types/fs-extra": "^9.0.5",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/js-yaml": "^3.12.5",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
"@types/nodegit": "^0.26.12",
|
||||
"@types/recursive-readdir": "^2.2.0"
|
||||
},
|
||||
"jest": {
|
||||
|
||||
@@ -16,13 +16,17 @@
|
||||
import fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getRootLogger, loadBackendConfig } from '@backstage/backend-common';
|
||||
import {
|
||||
getAzureHostToken,
|
||||
getGitHubRequestOptions,
|
||||
getGitLabRequestOptions,
|
||||
getAzureRequestOptions,
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
getGitHost,
|
||||
getGithubHostToken,
|
||||
getGitlabHostToken,
|
||||
getGitRepoType,
|
||||
getGitHubIntegrationConfig,
|
||||
getGitLabIntegrationConfig,
|
||||
getAzureIntegrationConfig,
|
||||
} from './git-auth';
|
||||
|
||||
interface IGitlabBranch {
|
||||
@@ -86,62 +90,15 @@ function getAzureApiUrl(url: string): URL {
|
||||
);
|
||||
}
|
||||
|
||||
function getGithubRequestOptions(config: Config, host: string): RequestInit {
|
||||
const headers: HeadersInit = {
|
||||
Accept: 'application/vnd.github.v3.raw',
|
||||
};
|
||||
|
||||
const token = getGithubHostToken(config, host);
|
||||
|
||||
if (token) {
|
||||
headers.Authorization = `token ${token}`;
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
function getGitlabRequestOptions(config: Config, host: string): RequestInit {
|
||||
const headers: HeadersInit = {
|
||||
'PRIVATE-TOKEN': '',
|
||||
};
|
||||
|
||||
const token = getGitlabHostToken(config, host);
|
||||
if (token) {
|
||||
headers['PRIVATE-TOKEN'] = token;
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
function getAzureRequestOptions(config: Config, host: string): RequestInit {
|
||||
const headers: HeadersInit = {};
|
||||
|
||||
const token = getAzureHostToken(config, host);
|
||||
|
||||
if (token !== '') {
|
||||
headers.Authorization = `Basic ${Buffer.from(`:${token}`, 'utf8').toString(
|
||||
'base64',
|
||||
)}`;
|
||||
}
|
||||
|
||||
const requestOptions: RequestInit = {
|
||||
headers,
|
||||
};
|
||||
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
async function getGithubDefaultBranch(
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> {
|
||||
const path = getGithubApiUrl(config, repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
const options = getGithubRequestOptions(config, host);
|
||||
|
||||
const integrationConfig = getGitHubIntegrationConfig(config, host);
|
||||
const options = getGitHubRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const raw = await fetch(path, options);
|
||||
@@ -169,9 +126,10 @@ async function getGitlabDefaultBranch(
|
||||
config: Config,
|
||||
): Promise<string> {
|
||||
const path = getGitlabApiUrl(repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
|
||||
const gitlabHost = getGitHost(repositoryUrl);
|
||||
const options = getGitlabRequestOptions(config, gitlabHost);
|
||||
const integrationConfig = getGitLabIntegrationConfig(config, host);
|
||||
const options = getGitLabRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const raw = await fetch(path, options);
|
||||
@@ -203,7 +161,9 @@ async function getAzureDefaultBranch(
|
||||
): Promise<string> {
|
||||
const path = getAzureApiUrl(repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
const options = getAzureRequestOptions(config, host);
|
||||
|
||||
const integrationConfig = getAzureIntegrationConfig(config, host);
|
||||
const options = getAzureRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const urlResponse = await fetch(path, options);
|
||||
@@ -235,12 +195,8 @@ async function getAzureDefaultBranch(
|
||||
|
||||
export const getDefaultBranch = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> => {
|
||||
// TODO(Rugvip): Config should not be loaded here, pass it in instead
|
||||
const config = await loadBackendConfig({
|
||||
logger: getRootLogger(),
|
||||
argv: process.argv,
|
||||
});
|
||||
const type = getGitRepoType(repositoryUrl);
|
||||
|
||||
try {
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
*/
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getRootLogger, loadBackendConfig } from '@backstage/backend-common';
|
||||
import {
|
||||
readGitHubIntegrationConfigs,
|
||||
readGitLabIntegrationConfigs,
|
||||
readAzureIntegrationConfigs,
|
||||
GitHubIntegrationConfig,
|
||||
GitLabIntegrationConfig,
|
||||
AzureIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
|
||||
export function getGitHost(url: string): string {
|
||||
const { resource } = parseGitUrl(url);
|
||||
@@ -34,83 +41,65 @@ export function getGitRepoType(url: string): string {
|
||||
return type;
|
||||
}
|
||||
|
||||
export function getGithubHostToken(
|
||||
export const getGitHubIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): string | undefined {
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.github') ?? [];
|
||||
|
||||
const hostConfig = providerConfigs.filter(
|
||||
providerConfig => providerConfig.getOptionalString('host') === host,
|
||||
): GitHubIntegrationConfig => {
|
||||
const allGitHubConfigs = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
);
|
||||
const token =
|
||||
hostConfig[0]?.getOptionalString('token') ??
|
||||
config.getOptionalString('catalog.processors.github.privateToken') ??
|
||||
config.getOptionalString('catalog.processors.githubApi.privateToken') ??
|
||||
process.env.GITHUB_TOKEN;
|
||||
const gitHubIntegrationConfig = allGitHubConfigs.find(v => v.host === host);
|
||||
if (!gitHubIntegrationConfig) {
|
||||
throw new Error(`Unable to locate GitHub integration for the host ${host}`);
|
||||
}
|
||||
return gitHubIntegrationConfig;
|
||||
};
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export function getGitlabHostToken(
|
||||
export const getGitLabIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): string | undefined {
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.gitlab') ?? [];
|
||||
|
||||
const hostConfig = providerConfigs.filter(
|
||||
providerConfig => providerConfig.getOptionalString('host') === host,
|
||||
): GitLabIntegrationConfig => {
|
||||
const allGitLabConfigs = readGitLabIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.gitlab') ?? [],
|
||||
);
|
||||
const token =
|
||||
hostConfig[0]?.getOptionalString('token') ??
|
||||
config.getOptionalString('catalog.processors.gitlab.privateToken') ??
|
||||
config.getOptionalString('catalog.processors.gitlabApi.privateToken') ??
|
||||
process.env.GITLAB_TOKEN;
|
||||
const gitLabIntegrationConfig = allGitLabConfigs.find(v => v.host === host);
|
||||
if (!gitLabIntegrationConfig) {
|
||||
throw new Error(`Unable to locate GitLab integration for the host ${host}`);
|
||||
}
|
||||
return gitLabIntegrationConfig;
|
||||
};
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export function getAzureHostToken(
|
||||
export const getAzureIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): string | undefined {
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.azure') ?? [];
|
||||
|
||||
const hostConfig = providerConfigs.filter(
|
||||
providerConfig => providerConfig.getOptionalString('host') === host,
|
||||
): AzureIntegrationConfig => {
|
||||
const allAzureIntegrationConfig = readAzureIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.azure') ?? [],
|
||||
);
|
||||
const token =
|
||||
hostConfig[0]?.getOptionalString('token') ??
|
||||
config.getOptionalString('catalog.processors.azureApi.privateToken') ??
|
||||
process.env.AZURE_TOKEN;
|
||||
|
||||
return token;
|
||||
}
|
||||
const azureIntegrationConfig = allAzureIntegrationConfig.find(
|
||||
v => v.host === host,
|
||||
);
|
||||
if (!azureIntegrationConfig) {
|
||||
throw new Error(`Unable to locate Azure integration for the host ${host}`);
|
||||
}
|
||||
return azureIntegrationConfig;
|
||||
};
|
||||
|
||||
export const getTokenForGitRepo = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string | undefined> => {
|
||||
// TODO(Rugvip): Config should not be loaded here, pass it in instead
|
||||
const config = await loadBackendConfig({
|
||||
logger: getRootLogger(),
|
||||
argv: process.argv,
|
||||
});
|
||||
|
||||
const host = getGitHost(repositoryUrl);
|
||||
const type = getGitRepoType(repositoryUrl);
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'github':
|
||||
return getGithubHostToken(config, host);
|
||||
return getGitHubIntegrationConfig(config, host).token;
|
||||
case 'gitlab':
|
||||
return getGitlabHostToken(config, host);
|
||||
return getGitLabIntegrationConfig(config, host).token;
|
||||
case 'azure/api':
|
||||
return getAzureHostToken(config, host);
|
||||
|
||||
return getAzureIntegrationConfig(config, host).token;
|
||||
default:
|
||||
throw new Error('Failed to get repository type');
|
||||
}
|
||||
|
||||
@@ -17,19 +17,15 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import NodeGit, { Clone, Repository } from 'nodegit';
|
||||
import fs from 'fs-extra';
|
||||
import { InputError, UrlReader, Git } from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getDefaultBranch } from './default-branch';
|
||||
import { getGitRepoType, getTokenForGitRepo } from './git-auth';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InputError, UrlReader } from '@backstage/backend-common';
|
||||
import { RemoteProtocol } from './stages/prepare/types';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// Enables core.longpaths on windows to prevent crashing when checking out repos with long foldernames and/or deep nesting
|
||||
// @ts-ignore
|
||||
NodeGit.Libgit2.opts(28, 1);
|
||||
|
||||
export type ParsedLocationAnnotation = {
|
||||
type: RemoteProtocol;
|
||||
target: string;
|
||||
@@ -94,6 +90,7 @@ export const getLocationForEntity = (
|
||||
|
||||
export const getGitRepositoryTempFolder = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> => {
|
||||
const parsedGitLocation = parseGitUrl(repositoryUrl);
|
||||
// removes .git from git location path
|
||||
@@ -102,6 +99,7 @@ export const getGitRepositoryTempFolder = async (
|
||||
if (!parsedGitLocation.ref) {
|
||||
parsedGitLocation.ref = await getDefaultBranch(
|
||||
parsedGitLocation.toString('https'),
|
||||
config,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,23 +116,68 @@ export const getGitRepositoryTempFolder = async (
|
||||
|
||||
export const checkoutGitRepository = async (
|
||||
repoUrl: string,
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): Promise<string> => {
|
||||
const parsedGitLocation = parseGitUrl(repoUrl);
|
||||
const repositoryTmpPath = await getGitRepositoryTempFolder(repoUrl);
|
||||
const token = await getTokenForGitRepo(repoUrl);
|
||||
const repositoryTmpPath = await getGitRepositoryTempFolder(repoUrl, config);
|
||||
const token = await getTokenForGitRepo(repoUrl, config);
|
||||
|
||||
// Initialize a git client
|
||||
let git = Git.fromAuth({ logger });
|
||||
|
||||
// Docs about why username and password are set to these specific values.
|
||||
// https://isomorphic-git.org/docs/en/onAuth#oauth2-tokens
|
||||
if (token) {
|
||||
const type = getGitRepoType(repoUrl);
|
||||
switch (type) {
|
||||
case 'github':
|
||||
git = Git.fromAuth({
|
||||
username: token,
|
||||
password: 'x-oauth-basic',
|
||||
logger,
|
||||
});
|
||||
parsedGitLocation.token = `${token}:x-oauth-basic`;
|
||||
break;
|
||||
case 'gitlab':
|
||||
git = Git.fromAuth({
|
||||
username: 'oauth2',
|
||||
password: token,
|
||||
logger,
|
||||
});
|
||||
parsedGitLocation.token = `dummyUsername:${token}`;
|
||||
parsedGitLocation.git_suffix = true;
|
||||
break;
|
||||
case 'azure/api':
|
||||
git = Git.fromAuth({
|
||||
username: 'notempty',
|
||||
password: token,
|
||||
logger: logger,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
parsedGitLocation.token = `:${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Pull from repository if it has already been cloned.
|
||||
if (fs.existsSync(repositoryTmpPath)) {
|
||||
try {
|
||||
const repository = await Repository.open(repositoryTmpPath);
|
||||
const currentBranchName = (
|
||||
await repository.getCurrentBranch()
|
||||
).shorthand();
|
||||
await repository.fetch('origin');
|
||||
await repository.mergeBranches(
|
||||
currentBranchName,
|
||||
`origin/${currentBranchName}`,
|
||||
);
|
||||
const currentBranchName = await git.currentBranch({
|
||||
dir: repositoryTmpPath,
|
||||
});
|
||||
|
||||
await git.fetch({ dir: repositoryTmpPath, remote: 'origin' });
|
||||
await git.merge({
|
||||
dir: repositoryTmpPath,
|
||||
theirs: `origin/${currentBranchName}`,
|
||||
ours: currentBranchName || undefined,
|
||||
author: { name: 'Backstage TechDocs', email: 'techdocs@backstage.io' },
|
||||
committer: {
|
||||
name: 'Backstage TechDocs',
|
||||
email: 'techdocs@backstage.io',
|
||||
},
|
||||
});
|
||||
return repositoryTmpPath;
|
||||
} catch (e) {
|
||||
logger.info(
|
||||
@@ -144,40 +187,30 @@ export const checkoutGitRepository = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (token) {
|
||||
const type = getGitRepoType(repoUrl);
|
||||
switch (type) {
|
||||
case 'gitlab':
|
||||
// Personal Access Token
|
||||
parsedGitLocation.token = `dummyUsername:${token}`;
|
||||
parsedGitLocation.git_suffix = true;
|
||||
break;
|
||||
case 'github':
|
||||
parsedGitLocation.token = `${token}:x-oauth-basic`;
|
||||
break;
|
||||
default:
|
||||
parsedGitLocation.token = `:${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
const repositoryCheckoutUrl = parsedGitLocation.toString('https');
|
||||
|
||||
fs.mkdirSync(repositoryTmpPath, { recursive: true });
|
||||
await Clone.clone(repositoryCheckoutUrl, repositoryTmpPath);
|
||||
await git.clone({ url: repositoryCheckoutUrl, dir: repositoryTmpPath });
|
||||
|
||||
return repositoryTmpPath;
|
||||
};
|
||||
|
||||
export const getLastCommitTimestamp = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): Promise<number> => {
|
||||
const repositoryLocation = await checkoutGitRepository(repositoryUrl, logger);
|
||||
const repositoryLocation = await checkoutGitRepository(
|
||||
repositoryUrl,
|
||||
config,
|
||||
logger,
|
||||
);
|
||||
|
||||
const repository = await Repository.open(repositoryLocation);
|
||||
const commit = await repository.getReferenceCommit('HEAD');
|
||||
const git = Git.fromAuth({ logger });
|
||||
const sha = await git.resolveRef({ dir: repositoryLocation, ref: 'HEAD' });
|
||||
const commit = await git.readCommit({ dir: repositoryLocation, sha });
|
||||
|
||||
return commit.date().getTime();
|
||||
return commit.commit.committer.timestamp;
|
||||
};
|
||||
|
||||
export const getDocFilesFromRepository = async (
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { CommonGitPreparer } from './commonGit';
|
||||
import { checkoutGitRepository } from '../../helpers';
|
||||
|
||||
@@ -43,11 +44,13 @@ const createMockEntity = (annotations = {}) => {
|
||||
};
|
||||
};
|
||||
|
||||
const mockConfig = new ConfigReader({});
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
describe('commonGit preparer', () => {
|
||||
it('should prepare temp docs path from github repo', async () => {
|
||||
const preparer = new CommonGitPreparer(logger);
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
@@ -62,7 +65,7 @@ describe('commonGit preparer', () => {
|
||||
});
|
||||
|
||||
it('should prepare temp docs path from gitlab repo', async () => {
|
||||
const preparer = new CommonGitPreparer(logger);
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
@@ -77,7 +80,7 @@ describe('commonGit preparer', () => {
|
||||
});
|
||||
|
||||
it('should prepare temp docs path from azure repo', async () => {
|
||||
const preparer = new CommonGitPreparer(logger);
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
|
||||
@@ -14,17 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import path from 'path';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { PreparerBase } from './types';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { PreparerBase } from './types';
|
||||
import { parseReferenceAnnotation, checkoutGitRepository } from '../../helpers';
|
||||
|
||||
import { Logger } from 'winston';
|
||||
|
||||
export class CommonGitPreparer implements PreparerBase {
|
||||
private readonly config: Config;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger) {
|
||||
constructor(config: Config, logger: Logger) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -35,7 +38,11 @@ export class CommonGitPreparer implements PreparerBase {
|
||||
);
|
||||
|
||||
try {
|
||||
const repoPath = await checkoutGitRepository(target, this.logger);
|
||||
const repoPath = await checkoutGitRepository(
|
||||
target,
|
||||
this.config,
|
||||
this.logger,
|
||||
);
|
||||
const parsedGitLocation = parseGitUrl(target);
|
||||
|
||||
return path.join(repoPath, parsedGitLocation.filepath);
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { DirectoryPreparer } from './dir';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { DirectoryPreparer } from './dir';
|
||||
import { checkoutGitRepository } from '../../helpers';
|
||||
|
||||
function normalizePath(path: string) {
|
||||
@@ -44,9 +45,11 @@ const createMockEntity = (annotations: {}) => {
|
||||
};
|
||||
};
|
||||
|
||||
const mockConfig = new ConfigReader({});
|
||||
|
||||
describe('directory preparer', () => {
|
||||
it('should merge managed-by-location and techdocs-ref when techdocs-ref is relative', async () => {
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/managed-by-location':
|
||||
@@ -60,7 +63,7 @@ describe('directory preparer', () => {
|
||||
});
|
||||
|
||||
it('should merge managed-by-location and techdocs-ref when techdocs-ref is absolute', async () => {
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/managed-by-location':
|
||||
@@ -74,7 +77,7 @@ describe('directory preparer', () => {
|
||||
});
|
||||
|
||||
it('should merge managed-by-location and techdocs-ref when managed-by-location is a git repository', async () => {
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/managed-by-location':
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import { PreparerBase } from './types';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import path from 'path';
|
||||
import { parseReferenceAnnotation, checkoutGitRepository } from '../../helpers';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
@@ -22,9 +23,11 @@ import parseGitUrl from 'git-url-parse';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
export class DirectoryPreparer implements PreparerBase {
|
||||
private readonly config: Config;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger) {
|
||||
constructor(config: Config, logger: Logger) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -43,7 +46,11 @@ export class DirectoryPreparer implements PreparerBase {
|
||||
case 'url':
|
||||
case 'azure/api': {
|
||||
const parsedGitLocation = parseGitUrl(target);
|
||||
const repoLocation = await checkoutGitRepository(target, this.logger);
|
||||
const repoLocation = await checkoutGitRepository(
|
||||
target,
|
||||
this.config,
|
||||
this.logger,
|
||||
);
|
||||
|
||||
return path.dirname(
|
||||
path.join(repoLocation, parsedGitLocation.filepath),
|
||||
|
||||
@@ -30,18 +30,15 @@ export class Preparers implements PreparerBuilder {
|
||||
private preparerMap = new Map<RemoteProtocol, PreparerBase>();
|
||||
|
||||
static async fromConfig(
|
||||
// @ts-ignore
|
||||
// Config not used now, but will be used in urlPreparer when it starts using
|
||||
// @backstage/integration to get the tokens for providers.
|
||||
config: Config,
|
||||
{ logger, reader }: factoryOptions,
|
||||
): Promise<PreparerBuilder> {
|
||||
const preparers = new Preparers();
|
||||
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(config, logger);
|
||||
preparers.register('dir', directoryPreparer);
|
||||
|
||||
const commonGitPreparer = new CommonGitPreparer(logger);
|
||||
const commonGitPreparer = new CommonGitPreparer(config, logger);
|
||||
preparers.register('github', commonGitPreparer);
|
||||
preparers.register('gitlab', commonGitPreparer);
|
||||
preparers.register('azure/api', commonGitPreparer);
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Logger } from 'winston';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { PreparerBase } from './types';
|
||||
import { getDocFilesFromRepository } from '../../helpers';
|
||||
import { Logger } from 'winston';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
|
||||
export class UrlPreparer implements PreparerBase {
|
||||
private readonly logger: Logger;
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import mockFs from 'mock-fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
|
||||
|
||||
describe('getHeadersForFileExtension', () => {
|
||||
@@ -39,9 +41,11 @@ describe('getHeadersForFileExtension', () => {
|
||||
});
|
||||
|
||||
describe('getFileTreeRecursively', () => {
|
||||
const root = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'/rootDir': {
|
||||
[root]: {
|
||||
file1: '',
|
||||
subDirA: {
|
||||
file2: '',
|
||||
@@ -57,9 +61,9 @@ describe('getFileTreeRecursively', () => {
|
||||
});
|
||||
|
||||
it('returns complete file tree of a path', async () => {
|
||||
const fileList = await getFileTreeRecursively('/rootDir');
|
||||
const fileList = await getFileTreeRecursively(root);
|
||||
expect(fileList.length).toBe(2);
|
||||
expect(fileList).toContain('/rootDir/file1');
|
||||
expect(fileList).toContain('/rootDir/subDirA/file2');
|
||||
expect(fileList).toContain(path.resolve(root, 'file1'));
|
||||
expect(fileList).toContain(path.resolve(root, 'subDirA/file2'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/plugin-api-docs
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9161531b2: Link register API to catalog-import plugin
|
||||
- Updated dependencies [a08c32ced]
|
||||
- Updated dependencies [7e0b8cac5]
|
||||
- Updated dependencies [87c0c53c2]
|
||||
- @backstage/core@0.4.3
|
||||
- @backstage/plugin-catalog@0.2.9
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"version": "0.4.1",
|
||||
"version": "0.4.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,8 +30,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.9",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@kyma-project/asyncapi-react": "^0.14.2",
|
||||
"@material-icons/font": "^1.0.2",
|
||||
@@ -49,8 +49,8 @@
|
||||
"swagger-ui-react": "^3.37.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/plugin-auth-backend
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cc046682e: fix bug in token expiration date
|
||||
|
||||
## 0.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -64,7 +64,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.2",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express-session": "^1.17.2",
|
||||
|
||||
@@ -56,7 +56,9 @@ export const postMessageResponse = (
|
||||
var originInfo = {'type': 'config_info', 'targetOrigin': origin};
|
||||
(window.opener || window.parent).postMessage(originInfo, '*');
|
||||
(window.opener || window.parent).postMessage(JSON.parse(authResponse), origin);
|
||||
window.close();
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 100); // same as the interval of the core-api lib/loginPopup.ts (to address race conditions)
|
||||
`;
|
||||
const hash = crypto.createHash('sha256').update(script).digest('base64');
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/plugin-catalog-backend": "^0.5.0",
|
||||
"@backstage/integration": "^0.1.4",
|
||||
@@ -50,8 +50,8 @@
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @backstage/plugin-catalog
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7e0b8cac5: Add `CatalogIndexPage` and `CatalogEntityPage`, two new extensions that replace the existing `Router` component.
|
||||
|
||||
Add `EntityLayout` to replace `EntityPageLayout`, using children instead of an element property, and allowing for collection of all `RouteRef` mount points used within tabs.
|
||||
|
||||
Add `EntitySwitch` to be used to select components based on entity data, along with accompanying `isKind`, `isNamespace`, and `isComponentType` filters.
|
||||
|
||||
- 87c0c53c2: Add new `EntityProvider` component, which can be used to provide an entity for the `useEntity` hook.
|
||||
- Updated dependencies [a08c32ced]
|
||||
- Updated dependencies [359f9d2d8]
|
||||
- @backstage/core@0.4.3
|
||||
- @backstage/plugin-techdocs@0.5.2
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog",
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,9 +32,9 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^0.3.4",
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-scaffolder": "^0.3.5",
|
||||
"@backstage/plugin-techdocs": "^0.5.1",
|
||||
"@backstage/plugin-techdocs": "^0.5.2",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -52,8 +52,8 @@
|
||||
"swr": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@microsoft/microsoft-graph-types": "^1.25.0",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Outlet } from 'react-router';
|
||||
import { EntityProvider } from '../EntityProvider';
|
||||
import { EntityLoaderProvider } from '../EntityLoaderProvider';
|
||||
|
||||
export const CatalogEntityPage = () => {
|
||||
return (
|
||||
<EntityProvider>
|
||||
<EntityLoaderProvider>
|
||||
<Outlet />
|
||||
</EntityProvider>
|
||||
</EntityLoaderProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { ReactNode } from 'react';
|
||||
import { useEntityFromUrl, EntityContext } from '../../hooks/useEntity';
|
||||
|
||||
export const EntityLoaderProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { entity, loading, error } = useEntityFromUrl();
|
||||
|
||||
return (
|
||||
<EntityContext.Provider value={{ entity, loading, error }}>
|
||||
{children}
|
||||
</EntityContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { EntityLoaderProvider } from './EntityLoaderProvider';
|
||||
@@ -13,15 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { useEntityFromUrl, EntityContext } from '../../hooks/useEntity';
|
||||
import { EntityContext } from '../../hooks/useEntity';
|
||||
|
||||
export const EntityProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { entity, loading, error } = useEntityFromUrl();
|
||||
|
||||
return (
|
||||
<EntityContext.Provider value={{ entity, loading, error }}>
|
||||
{children}
|
||||
</EntityContext.Provider>
|
||||
);
|
||||
type EntityProviderProps = {
|
||||
entity: Entity;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const EntityProvider = ({ entity, children }: EntityProviderProps) => (
|
||||
<EntityContext.Provider
|
||||
value={{
|
||||
entity,
|
||||
loading: Boolean(entity),
|
||||
error: undefined,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</EntityContext.Provider>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ import { entityRoute, rootRoute } from '../routes';
|
||||
import { CatalogPage } from './CatalogPage';
|
||||
import { EntityNotFound } from './EntityNotFound';
|
||||
import { EntityPageLayout } from './EntityPageLayout';
|
||||
import { EntityProvider } from './EntityProvider';
|
||||
import { EntityLoaderProvider } from './EntityLoaderProvider';
|
||||
|
||||
const DefaultEntityPage = () => (
|
||||
<EntityPageLayout>
|
||||
@@ -79,9 +79,9 @@ export const Router = ({
|
||||
<Route
|
||||
path={`${entityRoute.path}`}
|
||||
element={
|
||||
<EntityProvider>
|
||||
<EntityLoaderProvider>
|
||||
<EntityPageSwitch EntityPage={EntityPage} />
|
||||
</EntityProvider>
|
||||
</EntityLoaderProvider>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
|
||||
@@ -55,7 +55,7 @@ export const useEntityFromUrl = (): EntityLoadingStatus => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Always going to return an entity, or throw an error if not a descendant of a EntityProvider.
|
||||
* Grab Entity from the context and its current loading state.
|
||||
*/
|
||||
export const useEntity = () => {
|
||||
const { entity, loading, error } = useContext<{
|
||||
|
||||
@@ -18,6 +18,7 @@ export * from '@backstage/catalog-client';
|
||||
export { AboutCard } from './components/AboutCard';
|
||||
export { EntityPageLayout } from './components/EntityPageLayout';
|
||||
export { EntityLayout } from './components/EntityLayout';
|
||||
export { EntityProvider } from './components/EntityProvider';
|
||||
export * from './components/EntitySwitch';
|
||||
export { Router } from './components/Router';
|
||||
export { useEntityCompoundName } from './components/useEntityCompoundName';
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -50,8 +50,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -49,8 +49,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-cost-insights
|
||||
|
||||
## 0.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3fca9adb9: Fix links in sample instructions
|
||||
- Updated dependencies [a08c32ced]
|
||||
- @backstage/core@0.4.3
|
||||
|
||||
## 0.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-cost-insights",
|
||||
"version": "0.5.3",
|
||||
"version": "0.5.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/test-utils": "^0.1.5",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -56,8 +56,8 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
+10
-10
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
import { CodeSnippet } from '@backstage/core';
|
||||
import { CodeSnippet, Link } from '@backstage/core';
|
||||
import { AlertInstructionsLayout } from '../AlertInstructionsLayout';
|
||||
|
||||
export const LabelDataflowInstructionsPage = () => {
|
||||
@@ -31,10 +31,10 @@ export const LabelDataflowInstructionsPage = () => {
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
In Cloud Dataflow, labels can be added to a job either programmatically
|
||||
or via the command-line when launching a job. Note that GCP has
|
||||
<a href="https://cloud.google.com/compute/docs/labeling-resources#restrictions">
|
||||
or via the command-line when launching a job. Note that GCP has{' '}
|
||||
<Link to="https://cloud.google.com/compute/docs/labeling-resources#restrictions">
|
||||
restrictions
|
||||
</a>{' '}
|
||||
</Link>{' '}
|
||||
on the length and characters that can be used in labels.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
@@ -46,9 +46,9 @@ export const LabelDataflowInstructionsPage = () => {
|
||||
<Typography variant="h3">DataflowPipelineOptions</Typography>
|
||||
<Typography paragraph>
|
||||
Dataflow jobs using Beam's{' '}
|
||||
<a href="https://beam.apache.org/releases/javadoc/2.3.0/org/apache/beam/runners/dataflow/options/DataflowPipelineOptions.html">
|
||||
<Link to="https://beam.apache.org/releases/javadoc/2.3.0/org/apache/beam/runners/dataflow/options/DataflowPipelineOptions.html">
|
||||
DataflowPipelineOptions
|
||||
</a>{' '}
|
||||
</Link>{' '}
|
||||
directly can use the <b>setLabels</b> function to add one or more
|
||||
labels:
|
||||
<CodeSnippet
|
||||
@@ -79,13 +79,13 @@ sc.optionsAs[DataflowPipelineOptions].setLabels(Map("job-id" -> "my-dataflow-job
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
For more information on specifying options, see the{' '}
|
||||
<a href="https://cloud.google.com/dataflow/docs/guides/specifying-exec-params">
|
||||
<Link to="https://cloud.google.com/dataflow/docs/guides/specifying-exec-params">
|
||||
Dataflow documentation
|
||||
</a>{' '}
|
||||
</Link>{' '}
|
||||
or{' '}
|
||||
<a href="https://spotify.github.io/scio/api/com/spotify/scio/ScioContext.html">
|
||||
<Link to="https://spotify.github.io/scio/api/com/spotify/scio/ScioContext.html">
|
||||
Scio Scaladoc
|
||||
</a>
|
||||
</Link>
|
||||
.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
+2
-2
@@ -135,7 +135,7 @@ export const ProjectGrowthInstructionsPage = () => {
|
||||
<Typography paragraph>
|
||||
Next, evaluate whether the growth is significant. This helps avoid
|
||||
premature optimization, where cost in engineering time is more than
|
||||
would be saved from the optimization over a reasonable timeframe.
|
||||
would be saved from the optimization over a reasonable time frame.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
We recommend reframing the cost growth itself in terms of engineering
|
||||
@@ -205,7 +205,7 @@ export const ProjectGrowthInstructionsPage = () => {
|
||||
Is the workload using cloud resources efficiently? For compute
|
||||
resources, do the utilization metrics look reasonable? Autoscaling
|
||||
infrastructure, such as Kubernetes, can run workloads more efficiently
|
||||
without comprimising reliability.
|
||||
without compromising reliability.
|
||||
</Typography>
|
||||
<Typography variant="h5">Lifecycle</Typography>
|
||||
<Typography paragraph>
|
||||
|
||||
@@ -24,7 +24,7 @@ export const DEFAULT_DURATION = Duration.P30D;
|
||||
* Derive the start date of a given period, assuming two repeating intervals.
|
||||
*
|
||||
* @param duration see comment on Duration enum
|
||||
* @param endDate from CostInsightsApi.getLastCompleteBillingDate + 1 day
|
||||
* @param exclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate + 1 day
|
||||
*/
|
||||
export function inclusiveStartDateOf(
|
||||
duration: Duration,
|
||||
|
||||
@@ -234,14 +234,24 @@ export function aggregationFor(
|
||||
'day',
|
||||
);
|
||||
|
||||
function nextDelta(): number {
|
||||
const varianceFromBaseline = 0.15;
|
||||
// Let's give positive vibes in trendlines - higher change for positive delta with >0.5 value
|
||||
const positiveTrendChance = 0.55;
|
||||
const normalization = positiveTrendChance - 1;
|
||||
return baseline * (Math.random() + normalization) * varianceFromBaseline;
|
||||
}
|
||||
|
||||
return [...Array(days).keys()].reduce(
|
||||
(values: DateAggregation[], i: number): DateAggregation[] => {
|
||||
const last = values.length ? values[values.length - 1].amount : baseline;
|
||||
const date = dayjs(inclusiveStartDateOf(duration, endDate))
|
||||
.add(i, 'day')
|
||||
.format(DEFAULT_DATE_FORMAT);
|
||||
const amount = Math.max(0, last + nextDelta());
|
||||
values.push({
|
||||
date: dayjs(inclusiveStartDateOf(duration, endDate))
|
||||
.add(i, 'day')
|
||||
.format(DEFAULT_DATE_FORMAT),
|
||||
amount: Math.max(0, last + (baseline / 20) * (Math.random() * 2 - 1)),
|
||||
date: date,
|
||||
amount: amount,
|
||||
});
|
||||
return values;
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"start": "backstage-cli plugin:serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -42,8 +42,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# @backstage/plugin-fossa
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7afdfef98: Bump dependency versions of @backstage/core, cli and test-utils
|
||||
- Updated dependencies [a08c32ced]
|
||||
- @backstage/core@0.4.3
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-fossa",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -43,8 +43,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -41,8 +41,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/core-api": "^0.2.7",
|
||||
"@backstage/plugin-catalog": "^0.2.8",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
@@ -51,8 +51,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -42,8 +42,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -43,8 +43,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -46,8 +46,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-kubernetes-backend": "^0.2.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@kubernetes/client-node": "^0.12.1",
|
||||
@@ -46,8 +46,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/core-api": "^0.2.6",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
@@ -47,8 +47,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -41,8 +41,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -33,8 +33,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -44,8 +44,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/plugin-register-component
|
||||
|
||||
## 0.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1517876fd: Register component plugin is deprecated in favor of @backstage/plugin-catalog-import
|
||||
- Updated dependencies [a08c32ced]
|
||||
- Updated dependencies [7e0b8cac5]
|
||||
- Updated dependencies [87c0c53c2]
|
||||
- @backstage/core@0.4.3
|
||||
- @backstage/plugin-catalog@0.2.9
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-register-component",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,8 +31,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.9",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -45,8 +45,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -47,8 +47,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"command-exists-promise": "^2.0.2",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"dockerode": "^3.2.1",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
@@ -49,20 +50,18 @@
|
||||
"git-url-parse": "^11.4.3",
|
||||
"globby": "^11.0.0",
|
||||
"helmet": "^4.0.0",
|
||||
"isomorphic-git": "^1.8.0",
|
||||
"jsonschema": "^1.2.6",
|
||||
"morgan": "^1.10.0",
|
||||
"nodegit": "0.27.0",
|
||||
"uuid": "^8.2.0",
|
||||
"winston": "^3.2.1",
|
||||
"yaml": "^1.10.0",
|
||||
"cross-fetch": "^3.0.6"
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@octokit/types": "^5.4.1",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/nodegit": "0.26.11",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^4.0.2",
|
||||
"yaml": "^1.10.0"
|
||||
|
||||
@@ -14,11 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const mocks = {
|
||||
Clone: { clone: jest.fn() },
|
||||
CheckoutOptions: jest.fn(() => {}),
|
||||
};
|
||||
jest.doMock('nodegit', () => mocks);
|
||||
jest.doMock('fs-extra', () => ({
|
||||
promises: {
|
||||
mkdtemp: jest.fn(dir => `${dir}-static`),
|
||||
@@ -30,10 +25,16 @@ import {
|
||||
TemplateEntityV1alpha1,
|
||||
LOCATION_ANNOTATION,
|
||||
} from '@backstage/catalog-model';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { getVoidLogger, Git } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
describe('AzurePreparer', () => {
|
||||
const mockGitClient = {
|
||||
clone: jest.fn(),
|
||||
};
|
||||
|
||||
jest.spyOn(Git, 'fromAuth').mockReturnValue(mockGitClient as any);
|
||||
|
||||
let mockEntity: TemplateEntityV1alpha1;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -77,18 +78,7 @@ describe('AzurePreparer', () => {
|
||||
};
|
||||
});
|
||||
|
||||
it('calls the clone command with the correct arguments for a repository', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}));
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://dev.azure.com/backstage-org/backstage-project/_git/template-repo',
|
||||
expect.any(String),
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the clone command with the correct arguments if an access token is provided for a repository', async () => {
|
||||
it('initializes git client with the correct arguments if an access token is provided for a repository', async () => {
|
||||
const preparer = new AzurePreparer(
|
||||
new ConfigReader({
|
||||
scaffolder: {
|
||||
@@ -100,36 +90,44 @@ describe('AzurePreparer', () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
const logger = getVoidLogger();
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
username: 'notempty',
|
||||
password: 'fake-token',
|
||||
logger,
|
||||
});
|
||||
});
|
||||
it('calls the clone command with the correct arguments for a repository', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}));
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://dev.azure.com/backstage-org/backstage-project/_git/template-repo',
|
||||
expect.any(String),
|
||||
{
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: expect.anything(),
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url:
|
||||
'https://dev.azure.com/backstage-org/backstage-project/_git/template-repo',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('calls the clone command with the correct arguments for a repository when no path is provided', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}));
|
||||
delete mockEntity.spec.path;
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://dev.azure.com/backstage-org/backstage-project/_git/template-repo',
|
||||
expect.any(String),
|
||||
{},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url:
|
||||
'https://dev.azure.com/backstage-org/backstage-project/_git/template-repo',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('return the temp directory with the path to the folder if it is specified', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}));
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
@@ -142,6 +140,7 @@ describe('AzurePreparer', () => {
|
||||
it('return the working directory with the path to the folder if it is specified', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}));
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
logger: getVoidLogger(),
|
||||
workingDirectory: '/workDir',
|
||||
|
||||
@@ -18,10 +18,9 @@ import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { parseLocationAnnotation } from '../helpers';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
import { InputError, Git } from '@backstage/backend-common';
|
||||
import { PreparerBase, PreparerOptions } from './types';
|
||||
import GitUriParser from 'git-url-parse';
|
||||
import { Clone, Cred } from 'nodegit';
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
export class AzurePreparer implements PreparerBase {
|
||||
@@ -38,6 +37,7 @@ export class AzurePreparer implements PreparerBase {
|
||||
): Promise<string> {
|
||||
const { protocol, location } = parseLocationAnnotation(template);
|
||||
const workingDirectory = opts?.workingDirectory ?? os.tmpdir();
|
||||
const { logger } = opts;
|
||||
|
||||
if (!['azure/api', 'url'].includes(protocol)) {
|
||||
throw new InputError(
|
||||
@@ -57,19 +57,20 @@ export class AzurePreparer implements PreparerBase {
|
||||
template.spec.path ?? '.',
|
||||
);
|
||||
|
||||
const options = this.privateToken
|
||||
? {
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: () =>
|
||||
// Username can anything but the empty string according to: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page#use-a-pat
|
||||
Cred.userpassPlaintextNew('notempty', this.privateToken),
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
// Username can be anything but the empty string according to:
|
||||
// https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page#use-a-pat
|
||||
const git = this.privateToken
|
||||
? Git.fromAuth({
|
||||
password: this.privateToken,
|
||||
username: 'notempty',
|
||||
logger,
|
||||
})
|
||||
: Git.fromAuth({ logger });
|
||||
|
||||
await Clone.clone(repositoryCheckoutUrl, tempDir, options);
|
||||
await git.clone({
|
||||
url: repositoryCheckoutUrl,
|
||||
dir: tempDir,
|
||||
});
|
||||
|
||||
return path.resolve(tempDir, templateDirectory);
|
||||
}
|
||||
|
||||
@@ -14,11 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const mocks = {
|
||||
Clone: { clone: jest.fn() },
|
||||
CheckoutOptions: jest.fn(() => {}),
|
||||
};
|
||||
jest.doMock('nodegit', () => mocks);
|
||||
jest.doMock('fs-extra', () => ({
|
||||
promises: {
|
||||
mkdtemp: jest.fn(dir => `${dir}-static`),
|
||||
@@ -30,10 +25,16 @@ import {
|
||||
TemplateEntityV1alpha1,
|
||||
LOCATION_ANNOTATION,
|
||||
} from '@backstage/catalog-model';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { getVoidLogger, Git } from '@backstage/backend-common';
|
||||
|
||||
describe('GitHubPreparer', () => {
|
||||
let mockEntity: TemplateEntityV1alpha1;
|
||||
const mockGitClient = {
|
||||
clone: jest.fn(),
|
||||
};
|
||||
|
||||
jest.spyOn(Git, 'fromAuth').mockReturnValue(mockGitClient as any);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockEntity = {
|
||||
@@ -77,28 +78,24 @@ describe('GitHubPreparer', () => {
|
||||
});
|
||||
it('calls the clone command with the correct arguments for a repository', async () => {
|
||||
const preparer = new GithubPreparer();
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
checkoutBranch: 'master',
|
||||
},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
it('calls the clone command with the correct arguments for a repository when no path is provided', async () => {
|
||||
const preparer = new GithubPreparer();
|
||||
delete mockEntity.spec.path;
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
checkoutBranch: 'master',
|
||||
},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('return the temp directory with the path to the folder if it is specified', async () => {
|
||||
@@ -128,19 +125,14 @@ describe('GitHubPreparer', () => {
|
||||
|
||||
it('calls the clone command with the token when provided', async () => {
|
||||
const preparer = new GithubPreparer({ token: 'abc' });
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
checkoutBranch: 'master',
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: expect.any(Function),
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
const logger = getVoidLogger();
|
||||
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
username: 'abc',
|
||||
password: 'x-oauth-basic',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,10 +18,9 @@ import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { parseLocationAnnotation } from '../helpers';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
import { InputError, Git } from '@backstage/backend-common';
|
||||
import { PreparerBase, PreparerOptions } from './types';
|
||||
import GitUriParser from 'git-url-parse';
|
||||
import { Clone, CloneOptions, Cred } from 'nodegit';
|
||||
|
||||
export class GithubPreparer implements PreparerBase {
|
||||
token?: string;
|
||||
@@ -36,7 +35,7 @@ export class GithubPreparer implements PreparerBase {
|
||||
): Promise<string> {
|
||||
const { protocol, location } = parseLocationAnnotation(template);
|
||||
const workingDirectory = opts?.workingDirectory ?? os.tmpdir();
|
||||
const { token } = this;
|
||||
const { logger } = opts;
|
||||
|
||||
if (!['github', 'url'].includes(protocol)) {
|
||||
throw new InputError(
|
||||
@@ -56,25 +55,21 @@ export class GithubPreparer implements PreparerBase {
|
||||
template.spec.path ?? '.',
|
||||
);
|
||||
|
||||
let cloneOptions: CloneOptions = {
|
||||
checkoutBranch: parsedGitLocation.ref,
|
||||
};
|
||||
const checkoutLocation = path.resolve(tempDir, templateDirectory);
|
||||
|
||||
if (token) {
|
||||
cloneOptions = {
|
||||
...cloneOptions,
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials() {
|
||||
return Cred.userpassPlaintextNew(token, 'x-oauth-basic');
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const git = this.token
|
||||
? Git.fromAuth({
|
||||
username: this.token,
|
||||
password: 'x-oauth-basic',
|
||||
logger,
|
||||
})
|
||||
: Git.fromAuth({ logger });
|
||||
|
||||
await Clone.clone(repositoryCheckoutUrl, tempDir, cloneOptions);
|
||||
await git.clone({
|
||||
url: repositoryCheckoutUrl,
|
||||
dir: tempDir,
|
||||
});
|
||||
|
||||
return path.resolve(tempDir, templateDirectory);
|
||||
return checkoutLocation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const mocks = {
|
||||
Clone: { clone: jest.fn() },
|
||||
CheckoutOptions: jest.fn(() => {}),
|
||||
};
|
||||
jest.doMock('nodegit', () => mocks);
|
||||
jest.doMock('fs-extra', () => ({
|
||||
promises: {
|
||||
mkdtemp: jest.fn(dir => `${dir}-static`),
|
||||
@@ -30,7 +25,7 @@ import {
|
||||
LOCATION_ANNOTATION,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { getVoidLogger, Git } from '@backstage/backend-common';
|
||||
|
||||
const mockEntityWithProtocol = (protocol: string): TemplateEntityV1alpha1 => ({
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
@@ -72,6 +67,12 @@ const mockEntityWithProtocol = (protocol: string): TemplateEntityV1alpha1 => ({
|
||||
|
||||
describe('GitLabPreparer', () => {
|
||||
let mockEntity: TemplateEntityV1alpha1;
|
||||
const mockGitClient = {
|
||||
clone: jest.fn(),
|
||||
};
|
||||
|
||||
jest.spyOn(Git, 'fromAuth').mockReturnValue(mockGitClient as any);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -80,13 +81,13 @@ describe('GitLabPreparer', () => {
|
||||
it(`calls the clone command with the correct arguments for a repository using the ${protocol} protocol`, async () => {
|
||||
const preparer = new GitlabPreparer(new ConfigReader({}));
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it(`calls the clone command with the correct arguments if an access token is provided in integrations for a repository using the ${protocol} protocol`, async () => {
|
||||
@@ -103,19 +104,15 @@ describe('GitLabPreparer', () => {
|
||||
}),
|
||||
);
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: expect.anything(),
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
const logger = getVoidLogger();
|
||||
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
username: 'oauth2',
|
||||
password: 'fake-token',
|
||||
});
|
||||
});
|
||||
|
||||
it(`calls the clone command with the correct arguments if an access token is provided in scaffolder for a repository using the ${protocol} protocol`, async () => {
|
||||
@@ -127,32 +124,28 @@ describe('GitLabPreparer', () => {
|
||||
}),
|
||||
);
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: expect.anything(),
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
const logger = getVoidLogger();
|
||||
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
username: 'oauth2',
|
||||
password: 'fake-token',
|
||||
});
|
||||
});
|
||||
|
||||
it(`calls the clone command with the correct arguments for a repository when no path is provided using the ${protocol} protocol`, async () => {
|
||||
const preparer = new GitlabPreparer(new ConfigReader({}));
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
delete mockEntity.spec.path;
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it(`return the temp directory with the path to the folder if it is specified using the ${protocol} protocol`, async () => {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
import { InputError, Git } from '@backstage/backend-common';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
} from '@backstage/integration';
|
||||
import fs from 'fs-extra';
|
||||
import GitUriParser from 'git-url-parse';
|
||||
import { Clone, Cred } from 'nodegit';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { parseLocationAnnotation } from '../helpers';
|
||||
@@ -46,6 +45,7 @@ export class GitlabPreparer implements PreparerBase {
|
||||
opts: PreparerOptions,
|
||||
): Promise<string> {
|
||||
const { protocol, location } = parseLocationAnnotation(template);
|
||||
const { logger } = opts;
|
||||
const workingDirectory = opts?.workingDirectory ?? os.tmpdir();
|
||||
|
||||
if (!['gitlab', 'gitlab/api', 'url'].includes(protocol)) {
|
||||
@@ -67,17 +67,18 @@ export class GitlabPreparer implements PreparerBase {
|
||||
);
|
||||
|
||||
const token = this.getToken(parsedGitLocation.resource);
|
||||
const options = token
|
||||
? {
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: () => Cred.userpassPlaintextNew('oauth2', token),
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
const git = token
|
||||
? Git.fromAuth({
|
||||
password: token,
|
||||
username: 'oauth2',
|
||||
logger,
|
||||
})
|
||||
: Git.fromAuth({ logger });
|
||||
|
||||
await Clone.clone(repositoryCheckoutUrl, tempDir, options);
|
||||
await git.clone({
|
||||
url: repositoryCheckoutUrl,
|
||||
dir: tempDir,
|
||||
});
|
||||
|
||||
return path.resolve(tempDir, templateDirectory);
|
||||
}
|
||||
|
||||
@@ -13,17 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
jest.mock('nodegit');
|
||||
jest.mock('azure-devops-node-api/GitApi');
|
||||
jest.mock('azure-devops-node-api/interfaces/GitInterfaces');
|
||||
jest.mock('./helpers', () => ({
|
||||
pushToRemoteUserPass: jest.fn(),
|
||||
}));
|
||||
jest.mock('./helpers');
|
||||
|
||||
import { AzurePublisher } from './azure';
|
||||
import { GitApi } from 'azure-devops-node-api/GitApi';
|
||||
import { pushToRemoteUserPass } from './helpers';
|
||||
import * as helpers from './helpers';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
|
||||
const { mockGitApi } = require('azure-devops-node-api/GitApi') as {
|
||||
mockGitApi: {
|
||||
@@ -33,6 +28,7 @@ const { mockGitApi } = require('azure-devops-node-api/GitApi') as {
|
||||
|
||||
describe('Azure Publisher', () => {
|
||||
const publisher = new AzurePublisher(new GitApi('', []), 'fake-token');
|
||||
const logger = getVoidLogger();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -50,6 +46,7 @@ describe('Azure Publisher', () => {
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -63,12 +60,12 @@ describe('Azure Publisher', () => {
|
||||
},
|
||||
'project',
|
||||
);
|
||||
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
|
||||
'/tmp/test',
|
||||
'https://dev.azure.com/organization/project/_git/repo',
|
||||
'notempty',
|
||||
'fake-token',
|
||||
);
|
||||
expect(helpers.initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: '/tmp/test',
|
||||
remoteUrl: 'https://dev.azure.com/organization/project/_git/repo',
|
||||
auth: { username: 'notempty', password: 'fake-token' },
|
||||
logger,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user