Merge branch 'master' into mob/sentryport

This commit is contained in:
Patrik Oldsberg
2020-12-29 17:01:52 +01:00
committed by GitHub
121 changed files with 1735 additions and 1114 deletions
-6
View File
@@ -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.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-register-component': patch
---
Register component plugin is deprecated in favor of @backstage/plugin-catalog-import
-9
View File
@@ -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.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-api-docs': patch
---
Link register API to catalog-import plugin
+7
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
Tweaked development log formatter to include extra fields at the end of each log line
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-fossa': patch
---
Bump dependency versions of @backstage/core, cli and test-utils
+1
View File
@@ -135,6 +135,7 @@ neuro
newrelic
nginx
Niklas
nodegit
nohoist
nonces
npm
+5 -5
View File
@@ -11,6 +11,11 @@ on:
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#scanning-pull-requests
paths-ignore:
- '**/*.md'
- '**/*.yml'
- '**/*.yaml'
schedule:
- cron: '0 8 * * 6'
@@ -36,11 +41,6 @@ jobs:
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -2,7 +2,7 @@
title: AWS Lambda
author: roadie.io
authorUrl: https://roadie.io
category: Monitoring
category: Infrastructure
description: View AWS Lambda functions for your components in Backstage.
documentation: https://roadie.io/backstage/plugins/aws-lambda
iconUrl: https://roadie.io/images/logos/lambda.png
@@ -2,7 +2,7 @@
title: Firebase Functions
author: roadie.io
authorUrl: https://roadie.io/
category: Monitoring
category: Infrastructure
description: View Firebase Functions details for your service in Backstage.
documentation: https://roadie.io/backstage/plugins/firebase-functions
iconUrl: https://roadie.io/images/logos/firebase.png
+10 -14
View File
@@ -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, were 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>
+1
View File
@@ -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",
+1
View File
@@ -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),
);
+321
View File
@@ -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,
});
});
});
});
+251
View File
@@ -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 });
}
+17
View File
@@ -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';
+6
View File
@@ -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
+4 -4
View File
@@ -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,
);
+8
View File
@@ -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
+2 -2
View File
@@ -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",
@@ -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);
};
+3 -19
View File
@@ -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 })();
}
+1 -1
View File
@@ -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';
+3 -6
View File
@@ -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;
};
+10
View File
@@ -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
+3 -3
View File
@@ -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",
+22
View File
@@ -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'),
+14 -9
View File
@@ -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,27 +39,27 @@
"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-circleci": "^0.2.5",
"@backstage/plugin-explore": "^0.2.2",
"@backstage/plugin-github-actions": "^0.2.6",
"@backstage/plugin-lighthouse": "^0.2.6",
"@backstage/plugin-proxy-backend": "^0.2.3",
"@backstage/plugin-register-component": "^0.2.5",
"@backstage/plugin-register-component": "^0.2.6",
"@backstage/plugin-rollbar-backend": "^0.1.5",
"@backstage/plugin-scaffolder": "^0.3.5",
"@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",
+11
View File
@@ -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
+4 -4
View File
@@ -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,9 +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.8",
"@backstage/plugin-catalog": "^0.2.9",
"@backstage/test-utils": "^0.1.5",
"@backstage/theme": "^0.2.2",
"@material-ui/core": "^4.11.0",
@@ -47,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"
},
+3 -3
View File
@@ -30,7 +30,7 @@ import {
OAuthRequestDialog,
AnyApiFactory,
IconComponent,
BackstageRoutes,
FlatRoutes,
attachComponentData,
} from '@backstage/core';
import SentimentDissatisfiedIcon from '@material-ui/icons/SentimentDissatisfied';
@@ -133,10 +133,10 @@ class DevAppBuilder {
<AppRouter>
<SidebarPage>
{sidebar}
<BackstageRoutes>
<FlatRoutes>
{this.routes}
{deprecatedAppRoutes}
</BackstageRoutes>
</FlatRoutes>
</SidebarPage>
</AppRouter>
</AppProvider>
+20
View File
@@ -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
+3 -4
View File
@@ -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": {
+18 -62
View File
@@ -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 {
+44 -55
View File
@@ -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');
}
+72 -39
View File
@@ -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'));
});
});
+11
View File
@@ -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
+5 -5
View File
@@ -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",
+6
View File
@@ -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
+2 -2
View File
@@ -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",
@@ -87,7 +87,7 @@ describe('TokenFactory', () => {
iat: expect.any(Number),
exp: expect.any(Number),
});
expect(payload.exp).toBe(payload.iat + keyDurationSeconds * 1000);
expect(payload.exp).toBe(payload.iat + keyDurationSeconds);
});
it('should generate new signing keys when the current one expires', async () => {
@@ -69,7 +69,7 @@ export class TokenFactory implements TokenIssuer {
const sub = params.claims.sub;
const aud = 'backstage';
const iat = Math.floor(Date.now() / MS_IN_S);
const exp = iat + this.keyDurationSeconds * MS_IN_S;
const exp = iat + this.keyDurationSeconds;
this.logger.info(`Issuing token for ${sub}`);
@@ -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');
+3 -3
View File
@@ -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",
+16
View File
@@ -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
+5 -5
View File
@@ -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",
+1 -1
View File
@@ -55,7 +55,7 @@ export const useEntityFromUrl = (): EntityLoadingStatus => {
};
/**
* Grab Entity from the context and it's current loading state.
* Grab Entity from the context and its current loading state.
*/
export const useEntity = () => {
const { entity, loading, error } = useContext<{
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+8
View File
@@ -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
+4 -4
View File
@@ -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",
@@ -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>
@@ -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>
+1 -1
View File
@@ -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,
+14 -4
View File
@@ -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;
},
+3 -3
View File
@@ -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",
+9
View File
@@ -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
+4 -4
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+11
View File
@@ -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
+5 -5
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -4
View File
@@ -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,
});
});
});
});
@@ -17,9 +17,9 @@
import { PublisherBase, PublisherOptions, PublisherResult } from './types';
import { GitApi } from 'azure-devops-node-api/GitApi';
import { GitRepositoryCreateOptions } from 'azure-devops-node-api/interfaces/GitInterfaces';
import { pushToRemoteUserPass } from './helpers';
import { JsonValue } from '@backstage/config';
import { RequiredTemplateValues } from '../templater';
import { initRepoAndPush } from './helpers';
export class AzurePublisher implements PublisherBase {
private readonly client: GitApi;
@@ -33,11 +33,21 @@ export class AzurePublisher implements PublisherBase {
async publish({
values,
directory,
logger,
}: PublisherOptions): Promise<PublisherResult> {
const remoteUrl = await this.createRemote(values);
await pushToRemoteUserPass(directory, remoteUrl, 'notempty', this.token);
const catalogInfoUrl = `${remoteUrl}?path=%2Fcatalog-info.yaml`;
await initRepoAndPush({
dir: directory,
remoteUrl,
auth: {
username: 'notempty',
password: this.token,
},
logger,
});
return { remoteUrl, catalogInfoUrl };
}
@@ -15,10 +15,7 @@
*/
jest.mock('@octokit/rest');
jest.mock('nodegit');
jest.mock('./helpers', () => ({
pushToRemoteUserPass: jest.fn(),
}));
jest.mock('./helpers');
import { Octokit } from '@octokit/rest';
import {
@@ -27,7 +24,8 @@ import {
UsersGetByUsernameResponseData,
} from '@octokit/types';
import { GithubPublisher } from './github';
import { pushToRemoteUserPass } from './helpers';
import { initRepoAndPush } from './helpers';
import { getVoidLogger } from '@backstage/backend-common';
const { mockGithubClient } = require('@octokit/rest') as {
mockGithubClient: {
@@ -38,6 +36,7 @@ const { mockGithubClient } = require('@octokit/rest') as {
};
describe('GitHub Publisher', () => {
const logger = getVoidLogger();
beforeEach(() => {
jest.clearAllMocks();
});
@@ -69,6 +68,7 @@ describe('GitHub Publisher', () => {
access: 'blam/team',
},
directory: '/tmp/test',
logger,
});
expect(result).toEqual({
@@ -91,12 +91,12 @@ describe('GitHub Publisher', () => {
repo: 'test',
permission: 'admin',
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'https://github.com/backstage/backstage.git',
'abc',
'x-oauth-basic',
);
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: '/tmp/test',
remoteUrl: 'https://github.com/backstage/backstage.git',
auth: { username: 'abc', password: 'x-oauth-basic' },
logger,
});
});
it('should use octokit to create a repo in the authed user if the organisation property is not set', async () => {
@@ -118,6 +118,7 @@ describe('GitHub Publisher', () => {
access: 'blam',
},
directory: '/tmp/test',
logger,
});
expect(result).toEqual({
@@ -132,12 +133,13 @@ describe('GitHub Publisher', () => {
private: false,
});
expect(mockGithubClient.repos.addCollaborator).not.toHaveBeenCalled();
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'https://github.com/backstage/backstage.git',
'abc',
'x-oauth-basic',
);
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: '/tmp/test',
remoteUrl: 'https://github.com/backstage/backstage.git',
auth: { username: 'abc', password: 'x-oauth-basic' },
logger,
});
});
});
@@ -161,6 +163,7 @@ describe('GitHub Publisher', () => {
description: 'description',
},
directory: '/tmp/test',
logger,
});
expect(result).toEqual({
@@ -181,12 +184,12 @@ describe('GitHub Publisher', () => {
username: 'bob',
permission: 'admin',
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'https://github.com/backstage/backstage.git',
'abc',
'x-oauth-basic',
);
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: '/tmp/test',
remoteUrl: 'https://github.com/backstage/backstage.git',
auth: { username: 'abc', password: 'x-oauth-basic' },
logger,
});
});
});
@@ -216,6 +219,7 @@ describe('GitHub Publisher', () => {
owner: 'bob',
},
directory: '/tmp/test',
logger,
});
expect(result).toEqual({
@@ -229,12 +233,12 @@ describe('GitHub Publisher', () => {
private: true,
visibility: 'internal',
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'https://github.com/backstage/backstage.git',
'abc',
'x-oauth-basic',
);
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: '/tmp/test',
remoteUrl: 'https://github.com/backstage/backstage.git',
auth: { username: 'abc', password: 'x-oauth-basic' },
logger,
});
});
});
@@ -263,6 +267,7 @@ describe('GitHub Publisher', () => {
owner: 'bob',
},
directory: '/tmp/test',
logger,
});
expect(result).toEqual({
@@ -276,12 +281,12 @@ describe('GitHub Publisher', () => {
name: 'test',
private: true,
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'https://github.com/backstage/backstage.git',
'abc',
'x-oauth-basic',
);
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: '/tmp/test',
remoteUrl: 'https://github.com/backstage/backstage.git',
auth: { username: 'abc', password: 'x-oauth-basic' },
logger,
});
});
});
});
@@ -16,7 +16,7 @@
import { PublisherBase, PublisherOptions, PublisherResult } from './types';
import { Octokit } from '@octokit/rest';
import { pushToRemoteUserPass } from './helpers';
import { initRepoAndPush } from './helpers';
import { JsonValue } from '@backstage/config';
import { RequiredTemplateValues } from '../templater';
@@ -46,14 +46,20 @@ export class GithubPublisher implements PublisherBase {
async publish({
values,
directory,
logger,
}: PublisherOptions): Promise<PublisherResult> {
const remoteUrl = await this.createRemote(values);
await pushToRemoteUserPass(
directory,
await initRepoAndPush({
dir: directory,
remoteUrl,
this.token,
'x-oauth-basic',
);
auth: {
username: this.token,
password: 'x-oauth-basic',
},
logger,
});
const catalogInfoUrl = remoteUrl.replace(
/\.git$/,
'/blob/master/catalog-info.yaml',
@@ -14,16 +14,14 @@
* limitations under the License.
*/
jest.mock('nodegit');
jest.mock('@gitbeaker/node');
jest.mock('./helpers', () => ({
pushToRemoteUserPass: jest.fn(),
}));
jest.mock('./helpers');
import { GitlabPublisher } from './gitlab';
import { Gitlab as GitlabAPI } from '@gitbeaker/core';
import { Gitlab } from '@gitbeaker/node';
import { pushToRemoteUserPass } from './helpers';
import { initRepoAndPush } from './helpers';
import { getVoidLogger } from '@backstage/backend-common';
const { mockGitlabClient } = require('@gitbeaker/node') as {
mockGitlabClient: {
@@ -34,6 +32,7 @@ const { mockGitlabClient } = require('@gitbeaker/node') as {
};
describe('GitLab Publisher', () => {
const logger = getVoidLogger();
const publisher = new GitlabPublisher(new Gitlab({}), 'fake-token');
beforeEach(() => {
@@ -56,6 +55,7 @@ describe('GitLab Publisher', () => {
owner: 'bob',
},
directory: '/tmp/test',
logger,
});
expect(result).toEqual({ remoteUrl: 'mockclone' });
@@ -63,12 +63,12 @@ describe('GitLab Publisher', () => {
namespace_id: 42,
name: 'test',
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'mockclone',
'oauth2',
'fake-token',
);
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: '/tmp/test',
remoteUrl: 'mockclone',
auth: { username: 'oauth2', password: 'fake-token' },
logger,
});
});
it('should use gitbeaker to create a repo in the authed user if the namespace property is not set', async () => {
@@ -86,6 +86,7 @@ describe('GitLab Publisher', () => {
owner: 'bob',
},
directory: '/tmp/test',
logger,
});
expect(result).toEqual({ remoteUrl: 'mockclone' });
@@ -94,12 +95,12 @@ describe('GitLab Publisher', () => {
namespace_id: 21,
name: 'test',
});
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
'/tmp/test',
'mockclone',
'oauth2',
'fake-token',
);
expect(initRepoAndPush).toHaveBeenCalledWith({
dir: '/tmp/test',
remoteUrl: 'mockclone',
auth: { username: 'oauth2', password: 'fake-token' },
logger,
});
});
});
});
@@ -16,8 +16,8 @@
import { PublisherBase, PublisherOptions, PublisherResult } from './types';
import { Gitlab } from '@gitbeaker/core';
import { pushToRemoteUserPass } from './helpers';
import { JsonValue } from '@backstage/config';
import { initRepoAndPush } from './helpers';
import { RequiredTemplateValues } from '../templater';
export class GitlabPublisher implements PublisherBase {
@@ -32,9 +32,19 @@ export class GitlabPublisher implements PublisherBase {
async publish({
values,
directory,
logger,
}: PublisherOptions): Promise<PublisherResult> {
const remoteUrl = await this.createRemote(values);
await pushToRemoteUserPass(directory, remoteUrl, 'oauth2', this.token);
await initRepoAndPush({
dir: directory,
remoteUrl,
auth: {
username: 'oauth2',
password: this.token,
},
logger,
});
return { remoteUrl };
}
@@ -1,112 +0,0 @@
/*
* 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('nodegit');
import * as NodeGit from 'nodegit';
import { pushToRemoteCred } from './helpers';
const {
Repository,
mockRepo,
mockIndex,
Signature,
Remote,
mockRemote,
Cred,
} = require('nodegit') as {
Repository: jest.Mocked<{ init: any }>;
Signature: jest.Mocked<{ now: any }>;
Cred: jest.Mocked<{ userpassPlaintextNew: any }>;
Remote: jest.Mocked<{ create: any }>;
mockIndex: jest.Mocked<NodeGit.Index>;
mockRepo: jest.Mocked<NodeGit.Repository>;
mockRemote: jest.Mocked<NodeGit.Remote>;
};
describe('pushToRemoteCred', () => {
beforeEach(() => {
jest.clearAllMocks();
});
const directory = '/tmp/test/dir';
const remote = 'mockclone';
const credentialsProvider = () =>
NodeGit.Cred.userpassPlaintextNew('username', 'password');
it('should call init on the repo with the directory', async () => {
await pushToRemoteCred(directory, remote, credentialsProvider);
expect(Repository.init).toHaveBeenCalledWith(directory, 0);
});
it('should call refresh index on the index and write the new files', async () => {
await pushToRemoteCred(directory, remote, credentialsProvider);
expect(mockRepo.refreshIndex).toHaveBeenCalled();
});
it('should call add all files and write', async () => {
await pushToRemoteCred(directory, remote, credentialsProvider);
expect(mockIndex.addAll).toHaveBeenCalled();
expect(mockIndex.write).toHaveBeenCalled();
expect(mockIndex.writeTree).toHaveBeenCalled();
});
it('should create a commit with on head with the right name and commiter', async () => {
const mockSignature = { mockSignature: 'bloblly' };
Signature.now.mockReturnValue(mockSignature);
await pushToRemoteCred(directory, remote, credentialsProvider);
expect(Signature.now).toHaveBeenCalledTimes(2);
expect(Signature.now).toHaveBeenCalledWith(
'Scaffolder',
'scaffolder@backstage.io',
);
expect(mockRepo.createCommit).toHaveBeenCalledWith(
'HEAD',
mockSignature,
mockSignature,
'initial commit',
'mockoid',
[],
);
});
it('creates a remote with the repo and remote', async () => {
await pushToRemoteCred(directory, remote, credentialsProvider);
expect(Remote.create).toHaveBeenCalledWith(mockRepo, 'origin', 'mockclone');
});
it('shoud push to the remote repo', async () => {
await pushToRemoteCred(directory, remote, credentialsProvider);
const [remotes, { callbacks }] = mockRemote.push.mock
.calls[0] as NodeGit.PushOptions[];
expect(remotes).toEqual(['refs/heads/master:refs/heads/master']);
callbacks?.credentials?.();
expect(Cred.userpassPlaintextNew).toHaveBeenCalledWith(
'username',
'password',
);
});
});
@@ -13,43 +13,57 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Repository, Remote, Signature, Cred } from 'nodegit';
export async function pushToRemoteCred(
directory: string,
remote: string,
credentialsProvider: () => Cred,
): Promise<void> {
const repo = await Repository.init(directory, 0);
const index = await repo.refreshIndex();
await index.addAll();
await index.write();
const oid = await index.writeTree();
await repo.createCommit(
'HEAD',
Signature.now('Scaffolder', 'scaffolder@backstage.io'),
Signature.now('Scaffolder', 'scaffolder@backstage.io'),
'initial commit',
oid,
[],
);
import globby from 'globby';
import { Logger } from 'winston';
import { Git } from '@backstage/backend-common';
const remoteRepo = await Remote.create(repo, 'origin', remote);
export async function initRepoAndPush({
dir,
remoteUrl,
auth,
logger,
}: {
dir: string;
remoteUrl: string;
auth: { username: string; password: string };
logger: Logger;
}): Promise<void> {
const git = Git.fromAuth({
username: auth.username,
password: auth.password,
logger,
});
await remoteRepo.push(['refs/heads/master:refs/heads/master'], {
callbacks: {
credentials: credentialsProvider,
},
await git.init({
dir,
});
const paths = await globby(['./**', './**/.*'], {
cwd: dir,
gitignore: true,
dot: true,
});
for (const filepath of paths) {
await git.add({ dir, filepath });
}
await git.commit({
dir,
message: 'Initial commit',
author: { name: 'Scaffolder', email: 'scaffolder@backstage.io' },
committer: { name: 'Scaffolder', email: 'scaffolder@backstage.io' },
});
await git.addRemote({
dir,
url: remoteUrl,
remote: 'origin',
});
await git.push({
dir,
remote: 'origin',
});
}
export async function pushToRemoteUserPass(
directory: string,
remote: string,
username: string,
password: string,
): Promise<void> {
return pushToRemoteCred(directory, remote, () =>
Cred.userpassPlaintextNew(username, password),
);
}

Some files were not shown because too many files have changed in this diff Show More