Merge branch 'master' into mob/sentryport
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
---
|
||||
'@backstage/core-api': patch
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Add `BackstageRoutes` component to replace the top-level `Routes` component from `react-router` within apps, removing the need for manually appending `/*` to paths or sorting routes.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-register-component': patch
|
||||
---
|
||||
|
||||
Register component plugin is deprecated in favor of @backstage/plugin-catalog-import
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Add `CatalogIndexPage` and `CatalogEntityPage`, two new extensions that replace the existing `Router` component.
|
||||
|
||||
Add `EntityLayout` to replace `EntityPageLayout`, using children instead of an element property, and allowing for collection of all `RouteRef` mount points used within tabs.
|
||||
|
||||
Add `EntitySwitch` to be used to select components based on entity data, along with accompanying `isKind`, `isNamespace`, and `isComponentType` filters.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-api-docs': patch
|
||||
---
|
||||
|
||||
Link register API to catalog-import plugin
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/techdocs-common': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Moving the Git actions to isomorphic-git instead of the node binding version of nodegit
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Tweaked development log formatter to include extra fields at the end of each log line
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-fossa': patch
|
||||
---
|
||||
|
||||
Bump dependency versions of @backstage/core, cli and test-utils
|
||||
@@ -135,6 +135,7 @@ neuro
|
||||
newrelic
|
||||
nginx
|
||||
Niklas
|
||||
nodegit
|
||||
nohoist
|
||||
nonces
|
||||
npm
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
@@ -17,22 +17,18 @@ const Background = props => {
|
||||
<Block small className="stripe-bottom bg-black-grey">
|
||||
<Block.Container style={{ justifyContent: 'flex-start' }}>
|
||||
<Block.TextBox>
|
||||
<Block.Title>See us in action.</Block.Title>
|
||||
<Block.Title>See us in action</Block.Title>
|
||||
<Block.Paragraph>
|
||||
To illustrate the potential of Backstage, we’re showing you{' '}
|
||||
<a href="https://labs.spotify.com/2020/04/21/how-we-use-backstage-at-spotify/">
|
||||
how we use it here at Spotify
|
||||
To explore the UI and basic features of Backstage firsthand, go
|
||||
to: <a href="https://demo.backstage.io">demo.backstage.io</a>.
|
||||
</Block.Paragraph>
|
||||
<Block.Paragraph>
|
||||
Watch the videos below to get an introduction to Backstage and to
|
||||
see how we use different plugins to customize{' '}
|
||||
<a href="https://engineering.atspotify.com/2020/04/21/how-we-use-backstage-at-spotify/">
|
||||
our internal version of Backstage at Spotify
|
||||
</a>
|
||||
. The videos below feature the custom plugins and templates that
|
||||
we built for our internal version of Backstage. You can use
|
||||
Backstage to build the developer portal you want — integrating the
|
||||
tools that you use in your own infrastructure ecosystem. (Or get
|
||||
started by building an open source plugin for Backstage that
|
||||
everyone can use, like our{' '}
|
||||
<a href="https://backstage.io/blog/2020/04/06/lighthouse-plugin">
|
||||
Lighthouse Plugin
|
||||
</a>
|
||||
.)
|
||||
.
|
||||
</Block.Paragraph>
|
||||
</Block.TextBox>
|
||||
<Block.Graphics>
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"fs-extra": "^9.0.1",
|
||||
"git-url-parse": "^11.4.3",
|
||||
"helmet": "^4.0.0",
|
||||
"isomorphic-git": "^1.8.0",
|
||||
"knex": "^0.21.6",
|
||||
"lodash": "^4.17.15",
|
||||
"logform": "^2.1.1",
|
||||
|
||||
@@ -24,3 +24,4 @@ export * from './reading';
|
||||
export * from './service';
|
||||
export * from './paths';
|
||||
export * from './hot';
|
||||
export * from './scm';
|
||||
|
||||
@@ -17,19 +17,23 @@ import * as winston from 'winston';
|
||||
import { TransformableInfo } from 'logform';
|
||||
|
||||
const coloredTemplate = (info: TransformableInfo) => {
|
||||
const { timestamp, level, message, plugin, service } = info;
|
||||
const { timestamp, level, message, plugin, service, ...fields } = info;
|
||||
const colorizer = winston.format.colorize();
|
||||
const prefix = plugin || service;
|
||||
const timestampColor = colorizer.colorize('timestamp', timestamp);
|
||||
const prefixColor = colorizer.colorize('prefix', prefix);
|
||||
|
||||
return `${timestampColor} ${prefixColor} ${level} ${message}`;
|
||||
const extraFields = Object.entries(fields)
|
||||
.map(([key, value]) => `${colorizer.colorize('field', `${key}`)}=${value}`)
|
||||
.join(' ');
|
||||
|
||||
return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`;
|
||||
};
|
||||
|
||||
export const coloredFormat = winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.colorize({
|
||||
colors: { timestamp: 'dim', prefix: 'blue' },
|
||||
colors: { timestamp: 'dim', prefix: 'blue', field: 'cyan' },
|
||||
}),
|
||||
winston.format.printf(coloredTemplate),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
jest.mock('isomorphic-git');
|
||||
jest.mock('isomorphic-git/http/node');
|
||||
jest.mock('fs-extra');
|
||||
|
||||
import * as isomorphic from 'isomorphic-git';
|
||||
import { Git } from './git';
|
||||
import http from 'isomorphic-git/http/node';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
describe('Git', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
describe('add', () => {
|
||||
it('should call isomorphic-git add with the correct arguments', async () => {
|
||||
const git = Git.fromAuth({});
|
||||
const dir = 'mockdirectory';
|
||||
const filepath = 'mockfile/path';
|
||||
|
||||
await git.add({ dir, filepath });
|
||||
|
||||
expect(isomorphic.add).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
filepath,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addRemote', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const git = Git.fromAuth({});
|
||||
const dir = 'mockdirectory';
|
||||
const remote = 'origin';
|
||||
const url = 'git@github.com/something/sads';
|
||||
|
||||
await git.addRemote({ dir, remote, url });
|
||||
|
||||
expect(isomorphic.addRemote).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
remote,
|
||||
url,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('commit', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const git = Git.fromAuth({});
|
||||
const dir = 'mockdirectory';
|
||||
const message = 'Inital Commit';
|
||||
const author = {
|
||||
name: 'author',
|
||||
email: 'test@backstage.io',
|
||||
};
|
||||
const committer = {
|
||||
name: 'comitter',
|
||||
email: 'test@backstage.io',
|
||||
};
|
||||
|
||||
await git.commit({ dir, message, author, committer });
|
||||
|
||||
expect(isomorphic.commit).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
message,
|
||||
author,
|
||||
committer,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clone', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const url = 'http://github.com/some/repo';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.clone({ url, dir });
|
||||
|
||||
expect(isomorphic.clone).toHaveBeenCalledWith({
|
||||
fs,
|
||||
http,
|
||||
url,
|
||||
dir,
|
||||
singleBranch: true,
|
||||
depth: 1,
|
||||
onProgress: expect.any(Function),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
onAuth: expect.any(Function),
|
||||
});
|
||||
});
|
||||
it('should pass a function that returns the authorization as the onAuth handler', async () => {
|
||||
const url = 'http://github.com/some/repo';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.clone({ url, dir });
|
||||
|
||||
const { onAuth } = ((isomorphic.clone as unknown) as jest.Mock<
|
||||
typeof isomorphic['clone']
|
||||
>).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
});
|
||||
});
|
||||
|
||||
describe('currentBranch', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
const fullName = true;
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.currentBranch({ dir, fullName });
|
||||
|
||||
expect(isomorphic.currentBranch).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
fullname: true,
|
||||
});
|
||||
|
||||
await git.currentBranch({ dir });
|
||||
|
||||
expect(isomorphic.currentBranch).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
fullname: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetch', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const remote = 'http://github.com/some/repo';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.fetch({ remote, dir });
|
||||
|
||||
expect(isomorphic.fetch).toHaveBeenCalledWith({
|
||||
fs,
|
||||
http,
|
||||
remote,
|
||||
dir,
|
||||
onProgress: expect.any(Function),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
onAuth: expect.any(Function),
|
||||
});
|
||||
});
|
||||
it('should pass a function that returns the authorization as the onAuth handler', async () => {
|
||||
const remote = 'http://github.com/some/repo';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.fetch({ remote, dir });
|
||||
|
||||
const { onAuth } = ((isomorphic.fetch as unknown) as jest.Mock<
|
||||
typeof isomorphic['fetch']
|
||||
>).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
});
|
||||
});
|
||||
|
||||
describe('init', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.init({ dir });
|
||||
|
||||
expect(isomorphic.init).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('merge', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
const author = {
|
||||
name: 'author',
|
||||
email: 'test@backstage.io',
|
||||
};
|
||||
const committer = {
|
||||
name: 'comitter',
|
||||
email: 'test@backstage.io',
|
||||
};
|
||||
const theirs = 'master';
|
||||
const ours = 'production';
|
||||
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.merge({ dir, theirs, ours, author, committer });
|
||||
|
||||
expect(isomorphic.merge).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
ours,
|
||||
theirs,
|
||||
author,
|
||||
committer,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('push', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const remote = 'origin';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.push({ dir, remote });
|
||||
|
||||
expect(isomorphic.push).toHaveBeenCalledWith({
|
||||
fs,
|
||||
http,
|
||||
remote,
|
||||
dir,
|
||||
onProgress: expect.any(Function),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
onAuth: expect.any(Function),
|
||||
});
|
||||
});
|
||||
it('should pass a function that returns the authorization as the onAuth handler', async () => {
|
||||
const remote = 'origin';
|
||||
const dir = '/some/mock/dir';
|
||||
const auth = {
|
||||
username: 'blob',
|
||||
password: 'hunter2',
|
||||
};
|
||||
const git = Git.fromAuth(auth);
|
||||
|
||||
await git.push({ remote, dir });
|
||||
|
||||
const { onAuth } = ((isomorphic.push as unknown) as jest.Mock<
|
||||
typeof isomorphic['push']
|
||||
>).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readCommit', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
const sha = 'as43bd7';
|
||||
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.readCommit({ dir, sha });
|
||||
|
||||
expect(isomorphic.readCommit).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
oid: sha,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRef', () => {
|
||||
it('should call isomorphic-git with the correct arguments', async () => {
|
||||
const dir = '/some/mock/dir';
|
||||
const ref = 'as43bd7';
|
||||
|
||||
const git = Git.fromAuth({});
|
||||
|
||||
await git.resolveRef({ dir, ref });
|
||||
|
||||
expect(isomorphic.resolveRef).toHaveBeenCalledWith({
|
||||
fs,
|
||||
dir,
|
||||
ref,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import git, {
|
||||
ProgressCallback,
|
||||
MergeResult,
|
||||
ReadCommitResult,
|
||||
} from 'isomorphic-git';
|
||||
import http from 'isomorphic-git/http/node';
|
||||
import fs from 'fs-extra';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
/*
|
||||
provider username password
|
||||
GitHub token 'x-oauth-basic'
|
||||
GitHub App token 'x-access-token'
|
||||
BitBucket 'x-token-auth' token
|
||||
GitLab 'oauth2' token
|
||||
From : https://isomorphic-git.org/docs/en/onAuth
|
||||
|
||||
Azure 'notempty' token
|
||||
*/
|
||||
export class Git {
|
||||
private constructor(
|
||||
private readonly config: {
|
||||
username?: string;
|
||||
password?: string;
|
||||
logger?: Logger;
|
||||
},
|
||||
) {}
|
||||
|
||||
async add({
|
||||
dir,
|
||||
filepath,
|
||||
}: {
|
||||
dir: string;
|
||||
filepath: string;
|
||||
}): Promise<void> {
|
||||
this.config.logger?.info(`Adding file {dir=${dir},filepath=${filepath}}`);
|
||||
|
||||
return git.add({ fs, dir, filepath });
|
||||
}
|
||||
|
||||
async addRemote({
|
||||
dir,
|
||||
url,
|
||||
remote,
|
||||
}: {
|
||||
dir: string;
|
||||
remote: string;
|
||||
url: string;
|
||||
}): Promise<void> {
|
||||
this.config.logger?.info(
|
||||
`Creating new remote {dir=${dir},remote=${remote},url=${url}}`,
|
||||
);
|
||||
return git.addRemote({ fs, dir, remote, url });
|
||||
}
|
||||
|
||||
async commit({
|
||||
dir,
|
||||
message,
|
||||
author,
|
||||
committer,
|
||||
}: {
|
||||
dir: string;
|
||||
message: string;
|
||||
author: { name: string; email: string };
|
||||
committer: { name: string; email: string };
|
||||
}): Promise<string> {
|
||||
this.config.logger?.info(
|
||||
`Committing file to repo {dir=${dir},message=${message}}`,
|
||||
);
|
||||
|
||||
return git.commit({ fs, dir, message, author, committer });
|
||||
}
|
||||
|
||||
async clone({ url, dir }: { url: string; dir: string }): Promise<void> {
|
||||
this.config.logger?.info(`Cloning repo {dir=${dir},url=${url}}`);
|
||||
return git.clone({
|
||||
fs,
|
||||
http,
|
||||
url,
|
||||
dir,
|
||||
singleBranch: true,
|
||||
depth: 1,
|
||||
onProgress: this.onProgressHandler(),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
onAuth: this.onAuth,
|
||||
});
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/currentBranch
|
||||
async currentBranch({
|
||||
dir,
|
||||
fullName,
|
||||
}: {
|
||||
dir: string;
|
||||
fullName?: boolean;
|
||||
}): Promise<string | undefined> {
|
||||
const fullname = fullName ?? false;
|
||||
return git.currentBranch({ fs, dir, fullname }) as Promise<
|
||||
string | undefined
|
||||
>;
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/fetch
|
||||
async fetch({
|
||||
dir,
|
||||
remote,
|
||||
}: {
|
||||
dir: string;
|
||||
remote?: string;
|
||||
}): Promise<void> {
|
||||
const remoteValue = remote ?? 'origin';
|
||||
this.config.logger?.info(
|
||||
`Fetching remote=${remoteValue} for repository {dir=${dir}}`,
|
||||
);
|
||||
await git.fetch({
|
||||
fs,
|
||||
http,
|
||||
dir,
|
||||
remote: remoteValue,
|
||||
onProgress: this.onProgressHandler(),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
onAuth: this.onAuth,
|
||||
});
|
||||
}
|
||||
|
||||
async init({ dir }: { dir: string }): Promise<void> {
|
||||
this.config.logger?.info(`Init git repository {dir=${dir}}`);
|
||||
|
||||
return git.init({
|
||||
fs,
|
||||
dir,
|
||||
});
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/merge
|
||||
async merge({
|
||||
dir,
|
||||
theirs,
|
||||
ours,
|
||||
author,
|
||||
committer,
|
||||
}: {
|
||||
dir: string;
|
||||
theirs: string;
|
||||
ours?: string;
|
||||
author: { name: string; email: string };
|
||||
committer: { name: string; email: string };
|
||||
}): Promise<MergeResult> {
|
||||
this.config.logger?.info(
|
||||
`Merging branch '${theirs}' into '${ours}' for repository {dir=${dir}}`,
|
||||
);
|
||||
|
||||
// If ours is undefined, current branch is used.
|
||||
return git.merge({
|
||||
fs,
|
||||
dir,
|
||||
ours,
|
||||
theirs,
|
||||
author,
|
||||
committer,
|
||||
});
|
||||
}
|
||||
|
||||
async push({ dir, remote }: { dir: string; remote: string }) {
|
||||
this.config.logger?.info(
|
||||
`Pushing directory to remote {dir=${dir},remote=${remote}}`,
|
||||
);
|
||||
return git.push({
|
||||
fs,
|
||||
dir,
|
||||
http,
|
||||
onProgress: this.onProgressHandler(),
|
||||
headers: {
|
||||
'user-agent': 'git/@isomorphic-git',
|
||||
},
|
||||
remote: remote,
|
||||
onAuth: this.onAuth,
|
||||
});
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/readCommit
|
||||
async readCommit({
|
||||
dir,
|
||||
sha,
|
||||
}: {
|
||||
dir: string;
|
||||
sha: string;
|
||||
}): Promise<ReadCommitResult> {
|
||||
return git.readCommit({ fs, dir, oid: sha });
|
||||
}
|
||||
|
||||
// https://isomorphic-git.org/docs/en/resolveRef
|
||||
async resolveRef({
|
||||
dir,
|
||||
ref,
|
||||
}: {
|
||||
dir: string;
|
||||
ref: string;
|
||||
}): Promise<string> {
|
||||
return git.resolveRef({ fs, dir, ref });
|
||||
}
|
||||
|
||||
private onAuth = () => ({
|
||||
username: this.config.username,
|
||||
password: this.config.password,
|
||||
});
|
||||
|
||||
private onProgressHandler = (): ProgressCallback => {
|
||||
let currentPhase = '';
|
||||
|
||||
return event => {
|
||||
if (currentPhase !== event.phase) {
|
||||
currentPhase = event.phase;
|
||||
this.config.logger?.info(event.phase);
|
||||
}
|
||||
const total = event.total
|
||||
? `${Math.round((event.loaded / event.total) * 100)}%`
|
||||
: event.loaded;
|
||||
this.config.logger?.debug(`status={${event.phase},total={${total}}}`);
|
||||
};
|
||||
};
|
||||
|
||||
static fromAuth = ({
|
||||
username,
|
||||
password,
|
||||
logger,
|
||||
}: {
|
||||
username?: string;
|
||||
password?: string;
|
||||
logger?: Logger;
|
||||
}) => new Git({ username, password, logger });
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Git } from './git';
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d45efbc9b: Fix typo in .app.listen.port config schema
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.4.3",
|
||||
"version": "0.4.4",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -113,8 +113,8 @@
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.4.1",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@types/diff": "^4.0.2",
|
||||
@@ -182,7 +182,7 @@
|
||||
"visibility": "frontend",
|
||||
"description": "The host that the frontend should be bound to. Only used for local development."
|
||||
},
|
||||
"post": {
|
||||
"port": {
|
||||
"type": "number",
|
||||
"visibility": "frontend",
|
||||
"description": "The port that the frontend should be bound to. Only used for local development."
|
||||
|
||||
@@ -14,20 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin';
|
||||
|
||||
describe('LinkedPackageResolvePlugin', () => {
|
||||
const root = os.platform() === 'win32' ? 'C:\\root' : '/root';
|
||||
|
||||
it('should re-write paths for external packages', () => {
|
||||
const plugin = new LinkedPackageResolvePlugin('/root/repo/node_modules', [
|
||||
{
|
||||
name: 'a',
|
||||
location: '/root/external-a',
|
||||
},
|
||||
{
|
||||
name: '@s/b',
|
||||
location: '/root/external-b',
|
||||
},
|
||||
]);
|
||||
const plugin = new LinkedPackageResolvePlugin(
|
||||
path.resolve(root, 'repo/node_modules'),
|
||||
[
|
||||
{
|
||||
name: 'a',
|
||||
location: path.resolve(root, 'external-a'),
|
||||
},
|
||||
{
|
||||
name: '@s/b',
|
||||
location: path.resolve(root, 'external-b'),
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const tapAsync = jest.fn();
|
||||
const doResolve = jest.fn();
|
||||
@@ -50,10 +57,10 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
const callbackX = jest.fn();
|
||||
tap(
|
||||
{
|
||||
request: '/root/repo/package/x/src/module.ts',
|
||||
path: '/root/repo/package/x/src',
|
||||
request: path.resolve(root, 'repo/package/x/src/module.ts'),
|
||||
path: path.resolve(root, 'repo/package/x/src'),
|
||||
context: {
|
||||
issuer: '/root/repo/package/x/src/index.ts',
|
||||
issuer: path.resolve(root, 'repo/package/x/src/index.ts'),
|
||||
},
|
||||
},
|
||||
'some-context',
|
||||
@@ -81,10 +88,10 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
const callbackA = jest.fn();
|
||||
tap(
|
||||
{
|
||||
request: '/root/external-a/src/module.ts',
|
||||
path: '/root/external-a/src',
|
||||
request: path.resolve(root, 'external-a/src/module.ts'),
|
||||
path: path.resolve(root, 'external-a/src'),
|
||||
context: {
|
||||
issuer: '/root/external-a/src/index.ts',
|
||||
issuer: path.resolve(root, 'external-a/src/index.ts'),
|
||||
},
|
||||
},
|
||||
'some-context',
|
||||
@@ -95,13 +102,16 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
expect(doResolve).toHaveBeenCalledWith(
|
||||
resolver.hooks.resolve,
|
||||
{
|
||||
request: '/root/external-a/src/module.ts',
|
||||
path: '/root/repo/node_modules/a/src',
|
||||
request: path.resolve(root, 'external-a/src/module.ts'),
|
||||
path: path.resolve(root, 'repo/node_modules/a/src'),
|
||||
context: {
|
||||
issuer: '/root/repo/node_modules/a/src/index.ts',
|
||||
issuer: path.resolve(root, 'repo/node_modules/a/src/index.ts'),
|
||||
},
|
||||
},
|
||||
'resolve /root/external-a/src/module.ts in /root/repo/node_modules/a',
|
||||
`resolve ${path.resolve(
|
||||
root,
|
||||
'external-a/src/module.ts',
|
||||
)} in ${path.resolve(root, 'repo/node_modules/a')}`,
|
||||
'some-context',
|
||||
callbackA,
|
||||
);
|
||||
@@ -110,8 +120,8 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
const callbackB = jest.fn();
|
||||
tap(
|
||||
{
|
||||
request: '/root/external-b/src/module.ts',
|
||||
path: '/root/external-b/src',
|
||||
request: path.resolve(root, 'external-b/src/module.ts'),
|
||||
path: path.resolve(root, 'external-b/src'),
|
||||
context: {
|
||||
issuer: false,
|
||||
},
|
||||
@@ -124,13 +134,16 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
expect(doResolve).toHaveBeenLastCalledWith(
|
||||
resolver.hooks.resolve,
|
||||
{
|
||||
request: '/root/external-b/src/module.ts',
|
||||
path: '/root/repo/node_modules/@s/b/src',
|
||||
request: path.resolve(root, 'external-b/src/module.ts'),
|
||||
path: path.resolve(root, 'repo/node_modules/@s/b/src'),
|
||||
context: {
|
||||
issuer: false,
|
||||
},
|
||||
},
|
||||
'resolve /root/external-b/src/module.ts in /root/repo/node_modules/@s/b',
|
||||
`resolve ${path.resolve(
|
||||
root,
|
||||
'external-b/src/module.ts',
|
||||
)} in ${path.resolve(root, 'repo/node_modules/@s/b')}`,
|
||||
'some-context',
|
||||
callbackB,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/core-api
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a08c32ced: Add `FlatRoutes` component to replace the top-level `Routes` component from `react-router` within apps, removing the need for manually appending `/*` to paths or sorting routes.
|
||||
- 86c3c652a: Deprecate `RouteRef` path parameter and member, and remove deprecated `routeRef.createSubRouteRef`.
|
||||
- 27f2af935: Delay auth loginPopup close to avoid race condition with callers of authFlowHelpers.
|
||||
|
||||
## 0.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-api",
|
||||
"description": "Internal Core API used by Backstage plugins and apps",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -43,7 +43,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/test-utils-core": "^0.1.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
+2
-4
@@ -66,13 +66,11 @@ function createRoutesFromChildren(children: ReactNode): RouteObject[] {
|
||||
});
|
||||
}
|
||||
|
||||
type BackstageRoutesProps = {
|
||||
type FlatRoutesProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const BackstageRoutes = (
|
||||
props: BackstageRoutesProps,
|
||||
): JSX.Element | null => {
|
||||
export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => {
|
||||
const routes = createRoutesFromChildren(props.children);
|
||||
return useRoutes(routes);
|
||||
};
|
||||
@@ -25,25 +25,15 @@ export class AbsoluteRouteRef<Params extends { [param in string]: string }> {
|
||||
|
||||
// TODO(Rugvip): Remove this, routes are looked up via the registry instead
|
||||
get path() {
|
||||
return this.config.path;
|
||||
return this.config.path ?? '';
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.config.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should not be used, create a separate RouteRef instead
|
||||
* @deprecated
|
||||
*/
|
||||
createSubRoute(): any {
|
||||
throw new Error(
|
||||
'This method should not be called, create a separate RouteRef instead',
|
||||
);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `routeRef{path=${this.path}}`;
|
||||
return `routeRef{title=${this.title}}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,13 +44,7 @@ export function createRouteRef<
|
||||
return new AbsoluteRouteRef<Params>(config);
|
||||
}
|
||||
|
||||
const create = Symbol('create-external-route-ref');
|
||||
|
||||
export class ExternalRouteRef {
|
||||
static [create]() {
|
||||
return new ExternalRouteRef();
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
toString() {
|
||||
@@ -69,5 +53,5 @@ export class ExternalRouteRef {
|
||||
}
|
||||
|
||||
export function createExternalRouteRef(): ExternalRouteRef {
|
||||
return ExternalRouteRef[create]();
|
||||
return new ((ExternalRouteRef as unknown) as { new (): ExternalRouteRef })();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ export type {
|
||||
ConcreteRoute,
|
||||
MutableRouteRef,
|
||||
} from './types';
|
||||
export { BackstageRoutes } from './BackstageRoutes';
|
||||
export { FlatRoutes } from './FlatRoutes';
|
||||
export { createRouteRef } from './RouteRef';
|
||||
export { useRouteRef } from './hooks';
|
||||
|
||||
@@ -19,14 +19,10 @@ import { IconComponent } from '../icons';
|
||||
// @ts-ignore, we're just embedding the Params type for usage in other places
|
||||
export type RouteRef<Params extends { [param in string]: string } = {}> = {
|
||||
// TODO(Rugvip): Remove path, look up via registry instead
|
||||
/** @deprecated paths are no longer accessed directly from RouteRefs, use useRouteRef instead */
|
||||
path: string;
|
||||
icon?: IconComponent;
|
||||
title: string;
|
||||
/**
|
||||
* This function should not be used, create a separate RouteRef instead
|
||||
* @deprecated
|
||||
*/
|
||||
createSubRoute(): any;
|
||||
};
|
||||
|
||||
export type AnyRouteRef = RouteRef<any>;
|
||||
@@ -51,7 +47,8 @@ export type MutableRouteRef = RouteRef<{}>;
|
||||
|
||||
export type RouteRefConfig<Params extends { [param in string]: string }> = {
|
||||
params?: Array<keyof Params>;
|
||||
path: string;
|
||||
/** @deprecated Route refs no longer decide their own path */
|
||||
path?: string;
|
||||
icon?: IconComponent;
|
||||
title: string;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/core
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a08c32ced: Add `FlatRoutes` component to replace the top-level `Routes` component from `react-router` within apps, removing the need for manually appending `/*` to paths or sorting routes.
|
||||
- Updated dependencies [a08c32ced]
|
||||
- Updated dependencies [86c3c652a]
|
||||
- Updated dependencies [27f2af935]
|
||||
- @backstage/core-api@0.2.8
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core",
|
||||
"description": "Core API used by Backstage plugins and apps",
|
||||
"version": "0.4.2",
|
||||
"version": "0.4.3",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core-api": "^0.2.7",
|
||||
"@backstage/core-api": "^0.2.8",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -65,7 +65,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# @backstage/create-app
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4e0e3b1bf: Add missing `yarn clean` for app.
|
||||
|
||||
For users with existing Backstage installations, add the following under the `scripts` section in `packages/app/package.json`, after the "lint" entry:
|
||||
|
||||
```json
|
||||
"clean": "backstage-cli clean",
|
||||
```
|
||||
|
||||
This will add the missing `yarn clean` for the generated frontend.
|
||||
|
||||
- 352a6581f: Added `"start-backend"` script to root `package.json`.
|
||||
|
||||
To apply this change to an existing app, add the following script to the root `package.json`:
|
||||
|
||||
```json
|
||||
"start-backend": "yarn workspace backend start"
|
||||
```
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -24,6 +24,8 @@ const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src'));
|
||||
if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) {
|
||||
require('..');
|
||||
} else {
|
||||
// Only used for development, so should be a devDependency
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
require('ts-node').register({
|
||||
transpileOnly: true,
|
||||
project: path.resolve(__dirname, '../../../tsconfig.json'),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/create-app",
|
||||
"description": "Create app package for Backstage",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -39,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",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/dev-utils
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 696b8ce74: Add new `addPage` method for use with extensions, as well as an `EntityGridItem` to easily create different test cases for entity overview cards.
|
||||
- Updated dependencies [a08c32ced]
|
||||
- Updated dependencies [7e0b8cac5]
|
||||
- Updated dependencies [87c0c53c2]
|
||||
- @backstage/core@0.4.3
|
||||
- @backstage/plugin-catalog@0.2.9
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/dev-utils",
|
||||
"description": "Utilities for developing Backstage plugins.",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.7",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,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"
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @backstage/techdocs-common
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8804e8981: Using @backstage/integration package for GitHub/GitLab/Azure tokens and request options.
|
||||
|
||||
Most probably you do not have to make any changes in the app because of this change.
|
||||
However, if you are using the `DirectoryPreparer` or `CommonGitPreparer` exported by
|
||||
`@backstage/techdocs-common` package, you now need to add pass in a `config` (from `@backstage/config`)
|
||||
instance as argument.
|
||||
|
||||
```
|
||||
<!-- Before -->
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const commonGitPreparer = new CommonGitPreparer(logger);
|
||||
<!-- Now -->
|
||||
const directoryPreparer = new DirectoryPreparer(config, logger);
|
||||
const commonGitPreparer = new CommonGitPreparer(config, logger);
|
||||
```
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/techdocs-common",
|
||||
"description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -39,6 +39,7 @@
|
||||
"@backstage/backend-common": "^0.4.1",
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/integration": "^0.1.4",
|
||||
"@google-cloud/storage": "^5.6.0",
|
||||
"@types/dockerode": "^3.2.1",
|
||||
"@types/express": "^4.17.6",
|
||||
@@ -50,18 +51,16 @@
|
||||
"js-yaml": "^3.14.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"mock-fs": "^4.13.0",
|
||||
"nodegit": "^0.27.0",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@types/fs-extra": "^9.0.5",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/js-yaml": "^3.12.5",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
"@types/nodegit": "^0.26.12",
|
||||
"@types/recursive-readdir": "^2.2.0"
|
||||
},
|
||||
"jest": {
|
||||
|
||||
@@ -16,13 +16,17 @@
|
||||
import fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getRootLogger, loadBackendConfig } from '@backstage/backend-common';
|
||||
import {
|
||||
getAzureHostToken,
|
||||
getGitHubRequestOptions,
|
||||
getGitLabRequestOptions,
|
||||
getAzureRequestOptions,
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
getGitHost,
|
||||
getGithubHostToken,
|
||||
getGitlabHostToken,
|
||||
getGitRepoType,
|
||||
getGitHubIntegrationConfig,
|
||||
getGitLabIntegrationConfig,
|
||||
getAzureIntegrationConfig,
|
||||
} from './git-auth';
|
||||
|
||||
interface IGitlabBranch {
|
||||
@@ -86,62 +90,15 @@ function getAzureApiUrl(url: string): URL {
|
||||
);
|
||||
}
|
||||
|
||||
function getGithubRequestOptions(config: Config, host: string): RequestInit {
|
||||
const headers: HeadersInit = {
|
||||
Accept: 'application/vnd.github.v3.raw',
|
||||
};
|
||||
|
||||
const token = getGithubHostToken(config, host);
|
||||
|
||||
if (token) {
|
||||
headers.Authorization = `token ${token}`;
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
function getGitlabRequestOptions(config: Config, host: string): RequestInit {
|
||||
const headers: HeadersInit = {
|
||||
'PRIVATE-TOKEN': '',
|
||||
};
|
||||
|
||||
const token = getGitlabHostToken(config, host);
|
||||
if (token) {
|
||||
headers['PRIVATE-TOKEN'] = token;
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
function getAzureRequestOptions(config: Config, host: string): RequestInit {
|
||||
const headers: HeadersInit = {};
|
||||
|
||||
const token = getAzureHostToken(config, host);
|
||||
|
||||
if (token !== '') {
|
||||
headers.Authorization = `Basic ${Buffer.from(`:${token}`, 'utf8').toString(
|
||||
'base64',
|
||||
)}`;
|
||||
}
|
||||
|
||||
const requestOptions: RequestInit = {
|
||||
headers,
|
||||
};
|
||||
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
async function getGithubDefaultBranch(
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> {
|
||||
const path = getGithubApiUrl(config, repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
const options = getGithubRequestOptions(config, host);
|
||||
|
||||
const integrationConfig = getGitHubIntegrationConfig(config, host);
|
||||
const options = getGitHubRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const raw = await fetch(path, options);
|
||||
@@ -169,9 +126,10 @@ async function getGitlabDefaultBranch(
|
||||
config: Config,
|
||||
): Promise<string> {
|
||||
const path = getGitlabApiUrl(repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
|
||||
const gitlabHost = getGitHost(repositoryUrl);
|
||||
const options = getGitlabRequestOptions(config, gitlabHost);
|
||||
const integrationConfig = getGitLabIntegrationConfig(config, host);
|
||||
const options = getGitLabRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const raw = await fetch(path, options);
|
||||
@@ -203,7 +161,9 @@ async function getAzureDefaultBranch(
|
||||
): Promise<string> {
|
||||
const path = getAzureApiUrl(repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
const options = getAzureRequestOptions(config, host);
|
||||
|
||||
const integrationConfig = getAzureIntegrationConfig(config, host);
|
||||
const options = getAzureRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const urlResponse = await fetch(path, options);
|
||||
@@ -235,12 +195,8 @@ async function getAzureDefaultBranch(
|
||||
|
||||
export const getDefaultBranch = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> => {
|
||||
// TODO(Rugvip): Config should not be loaded here, pass it in instead
|
||||
const config = await loadBackendConfig({
|
||||
logger: getRootLogger(),
|
||||
argv: process.argv,
|
||||
});
|
||||
const type = getGitRepoType(repositoryUrl);
|
||||
|
||||
try {
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
*/
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getRootLogger, loadBackendConfig } from '@backstage/backend-common';
|
||||
import {
|
||||
readGitHubIntegrationConfigs,
|
||||
readGitLabIntegrationConfigs,
|
||||
readAzureIntegrationConfigs,
|
||||
GitHubIntegrationConfig,
|
||||
GitLabIntegrationConfig,
|
||||
AzureIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
|
||||
export function getGitHost(url: string): string {
|
||||
const { resource } = parseGitUrl(url);
|
||||
@@ -34,83 +41,65 @@ export function getGitRepoType(url: string): string {
|
||||
return type;
|
||||
}
|
||||
|
||||
export function getGithubHostToken(
|
||||
export const getGitHubIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): string | undefined {
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.github') ?? [];
|
||||
|
||||
const hostConfig = providerConfigs.filter(
|
||||
providerConfig => providerConfig.getOptionalString('host') === host,
|
||||
): GitHubIntegrationConfig => {
|
||||
const allGitHubConfigs = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
);
|
||||
const token =
|
||||
hostConfig[0]?.getOptionalString('token') ??
|
||||
config.getOptionalString('catalog.processors.github.privateToken') ??
|
||||
config.getOptionalString('catalog.processors.githubApi.privateToken') ??
|
||||
process.env.GITHUB_TOKEN;
|
||||
const gitHubIntegrationConfig = allGitHubConfigs.find(v => v.host === host);
|
||||
if (!gitHubIntegrationConfig) {
|
||||
throw new Error(`Unable to locate GitHub integration for the host ${host}`);
|
||||
}
|
||||
return gitHubIntegrationConfig;
|
||||
};
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export function getGitlabHostToken(
|
||||
export const getGitLabIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): string | undefined {
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.gitlab') ?? [];
|
||||
|
||||
const hostConfig = providerConfigs.filter(
|
||||
providerConfig => providerConfig.getOptionalString('host') === host,
|
||||
): GitLabIntegrationConfig => {
|
||||
const allGitLabConfigs = readGitLabIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.gitlab') ?? [],
|
||||
);
|
||||
const token =
|
||||
hostConfig[0]?.getOptionalString('token') ??
|
||||
config.getOptionalString('catalog.processors.gitlab.privateToken') ??
|
||||
config.getOptionalString('catalog.processors.gitlabApi.privateToken') ??
|
||||
process.env.GITLAB_TOKEN;
|
||||
const gitLabIntegrationConfig = allGitLabConfigs.find(v => v.host === host);
|
||||
if (!gitLabIntegrationConfig) {
|
||||
throw new Error(`Unable to locate GitLab integration for the host ${host}`);
|
||||
}
|
||||
return gitLabIntegrationConfig;
|
||||
};
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export function getAzureHostToken(
|
||||
export const getAzureIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): string | undefined {
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.azure') ?? [];
|
||||
|
||||
const hostConfig = providerConfigs.filter(
|
||||
providerConfig => providerConfig.getOptionalString('host') === host,
|
||||
): AzureIntegrationConfig => {
|
||||
const allAzureIntegrationConfig = readAzureIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.azure') ?? [],
|
||||
);
|
||||
const token =
|
||||
hostConfig[0]?.getOptionalString('token') ??
|
||||
config.getOptionalString('catalog.processors.azureApi.privateToken') ??
|
||||
process.env.AZURE_TOKEN;
|
||||
|
||||
return token;
|
||||
}
|
||||
const azureIntegrationConfig = allAzureIntegrationConfig.find(
|
||||
v => v.host === host,
|
||||
);
|
||||
if (!azureIntegrationConfig) {
|
||||
throw new Error(`Unable to locate Azure integration for the host ${host}`);
|
||||
}
|
||||
return azureIntegrationConfig;
|
||||
};
|
||||
|
||||
export const getTokenForGitRepo = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string | undefined> => {
|
||||
// TODO(Rugvip): Config should not be loaded here, pass it in instead
|
||||
const config = await loadBackendConfig({
|
||||
logger: getRootLogger(),
|
||||
argv: process.argv,
|
||||
});
|
||||
|
||||
const host = getGitHost(repositoryUrl);
|
||||
const type = getGitRepoType(repositoryUrl);
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'github':
|
||||
return getGithubHostToken(config, host);
|
||||
return getGitHubIntegrationConfig(config, host).token;
|
||||
case 'gitlab':
|
||||
return getGitlabHostToken(config, host);
|
||||
return getGitLabIntegrationConfig(config, host).token;
|
||||
case 'azure/api':
|
||||
return getAzureHostToken(config, host);
|
||||
|
||||
return getAzureIntegrationConfig(config, host).token;
|
||||
default:
|
||||
throw new Error('Failed to get repository type');
|
||||
}
|
||||
|
||||
@@ -17,19 +17,15 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import NodeGit, { Clone, Repository } from 'nodegit';
|
||||
import fs from 'fs-extra';
|
||||
import { InputError, UrlReader, Git } from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getDefaultBranch } from './default-branch';
|
||||
import { getGitRepoType, getTokenForGitRepo } from './git-auth';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InputError, UrlReader } from '@backstage/backend-common';
|
||||
import { RemoteProtocol } from './stages/prepare/types';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
// Enables core.longpaths on windows to prevent crashing when checking out repos with long foldernames and/or deep nesting
|
||||
// @ts-ignore
|
||||
NodeGit.Libgit2.opts(28, 1);
|
||||
|
||||
export type ParsedLocationAnnotation = {
|
||||
type: RemoteProtocol;
|
||||
target: string;
|
||||
@@ -94,6 +90,7 @@ export const getLocationForEntity = (
|
||||
|
||||
export const getGitRepositoryTempFolder = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> => {
|
||||
const parsedGitLocation = parseGitUrl(repositoryUrl);
|
||||
// removes .git from git location path
|
||||
@@ -102,6 +99,7 @@ export const getGitRepositoryTempFolder = async (
|
||||
if (!parsedGitLocation.ref) {
|
||||
parsedGitLocation.ref = await getDefaultBranch(
|
||||
parsedGitLocation.toString('https'),
|
||||
config,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,23 +116,68 @@ export const getGitRepositoryTempFolder = async (
|
||||
|
||||
export const checkoutGitRepository = async (
|
||||
repoUrl: string,
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): Promise<string> => {
|
||||
const parsedGitLocation = parseGitUrl(repoUrl);
|
||||
const repositoryTmpPath = await getGitRepositoryTempFolder(repoUrl);
|
||||
const token = await getTokenForGitRepo(repoUrl);
|
||||
const repositoryTmpPath = await getGitRepositoryTempFolder(repoUrl, config);
|
||||
const token = await getTokenForGitRepo(repoUrl, config);
|
||||
|
||||
// Initialize a git client
|
||||
let git = Git.fromAuth({ logger });
|
||||
|
||||
// Docs about why username and password are set to these specific values.
|
||||
// https://isomorphic-git.org/docs/en/onAuth#oauth2-tokens
|
||||
if (token) {
|
||||
const type = getGitRepoType(repoUrl);
|
||||
switch (type) {
|
||||
case 'github':
|
||||
git = Git.fromAuth({
|
||||
username: token,
|
||||
password: 'x-oauth-basic',
|
||||
logger,
|
||||
});
|
||||
parsedGitLocation.token = `${token}:x-oauth-basic`;
|
||||
break;
|
||||
case 'gitlab':
|
||||
git = Git.fromAuth({
|
||||
username: 'oauth2',
|
||||
password: token,
|
||||
logger,
|
||||
});
|
||||
parsedGitLocation.token = `dummyUsername:${token}`;
|
||||
parsedGitLocation.git_suffix = true;
|
||||
break;
|
||||
case 'azure/api':
|
||||
git = Git.fromAuth({
|
||||
username: 'notempty',
|
||||
password: token,
|
||||
logger: logger,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
parsedGitLocation.token = `:${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Pull from repository if it has already been cloned.
|
||||
if (fs.existsSync(repositoryTmpPath)) {
|
||||
try {
|
||||
const repository = await Repository.open(repositoryTmpPath);
|
||||
const currentBranchName = (
|
||||
await repository.getCurrentBranch()
|
||||
).shorthand();
|
||||
await repository.fetch('origin');
|
||||
await repository.mergeBranches(
|
||||
currentBranchName,
|
||||
`origin/${currentBranchName}`,
|
||||
);
|
||||
const currentBranchName = await git.currentBranch({
|
||||
dir: repositoryTmpPath,
|
||||
});
|
||||
|
||||
await git.fetch({ dir: repositoryTmpPath, remote: 'origin' });
|
||||
await git.merge({
|
||||
dir: repositoryTmpPath,
|
||||
theirs: `origin/${currentBranchName}`,
|
||||
ours: currentBranchName || undefined,
|
||||
author: { name: 'Backstage TechDocs', email: 'techdocs@backstage.io' },
|
||||
committer: {
|
||||
name: 'Backstage TechDocs',
|
||||
email: 'techdocs@backstage.io',
|
||||
},
|
||||
});
|
||||
return repositoryTmpPath;
|
||||
} catch (e) {
|
||||
logger.info(
|
||||
@@ -144,40 +187,30 @@ export const checkoutGitRepository = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (token) {
|
||||
const type = getGitRepoType(repoUrl);
|
||||
switch (type) {
|
||||
case 'gitlab':
|
||||
// Personal Access Token
|
||||
parsedGitLocation.token = `dummyUsername:${token}`;
|
||||
parsedGitLocation.git_suffix = true;
|
||||
break;
|
||||
case 'github':
|
||||
parsedGitLocation.token = `${token}:x-oauth-basic`;
|
||||
break;
|
||||
default:
|
||||
parsedGitLocation.token = `:${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
const repositoryCheckoutUrl = parsedGitLocation.toString('https');
|
||||
|
||||
fs.mkdirSync(repositoryTmpPath, { recursive: true });
|
||||
await Clone.clone(repositoryCheckoutUrl, repositoryTmpPath);
|
||||
await git.clone({ url: repositoryCheckoutUrl, dir: repositoryTmpPath });
|
||||
|
||||
return repositoryTmpPath;
|
||||
};
|
||||
|
||||
export const getLastCommitTimestamp = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): Promise<number> => {
|
||||
const repositoryLocation = await checkoutGitRepository(repositoryUrl, logger);
|
||||
const repositoryLocation = await checkoutGitRepository(
|
||||
repositoryUrl,
|
||||
config,
|
||||
logger,
|
||||
);
|
||||
|
||||
const repository = await Repository.open(repositoryLocation);
|
||||
const commit = await repository.getReferenceCommit('HEAD');
|
||||
const git = Git.fromAuth({ logger });
|
||||
const sha = await git.resolveRef({ dir: repositoryLocation, ref: 'HEAD' });
|
||||
const commit = await git.readCommit({ dir: repositoryLocation, sha });
|
||||
|
||||
return commit.date().getTime();
|
||||
return commit.commit.committer.timestamp;
|
||||
};
|
||||
|
||||
export const getDocFilesFromRepository = async (
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { CommonGitPreparer } from './commonGit';
|
||||
import { checkoutGitRepository } from '../../helpers';
|
||||
|
||||
@@ -43,11 +44,13 @@ const createMockEntity = (annotations = {}) => {
|
||||
};
|
||||
};
|
||||
|
||||
const mockConfig = new ConfigReader({});
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
describe('commonGit preparer', () => {
|
||||
it('should prepare temp docs path from github repo', async () => {
|
||||
const preparer = new CommonGitPreparer(logger);
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
@@ -62,7 +65,7 @@ describe('commonGit preparer', () => {
|
||||
});
|
||||
|
||||
it('should prepare temp docs path from gitlab repo', async () => {
|
||||
const preparer = new CommonGitPreparer(logger);
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
@@ -77,7 +80,7 @@ describe('commonGit preparer', () => {
|
||||
});
|
||||
|
||||
it('should prepare temp docs path from azure repo', async () => {
|
||||
const preparer = new CommonGitPreparer(logger);
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
|
||||
@@ -14,17 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import path from 'path';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { PreparerBase } from './types';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { PreparerBase } from './types';
|
||||
import { parseReferenceAnnotation, checkoutGitRepository } from '../../helpers';
|
||||
|
||||
import { Logger } from 'winston';
|
||||
|
||||
export class CommonGitPreparer implements PreparerBase {
|
||||
private readonly config: Config;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger) {
|
||||
constructor(config: Config, logger: Logger) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -35,7 +38,11 @@ export class CommonGitPreparer implements PreparerBase {
|
||||
);
|
||||
|
||||
try {
|
||||
const repoPath = await checkoutGitRepository(target, this.logger);
|
||||
const repoPath = await checkoutGitRepository(
|
||||
target,
|
||||
this.config,
|
||||
this.logger,
|
||||
);
|
||||
const parsedGitLocation = parseGitUrl(target);
|
||||
|
||||
return path.join(repoPath, parsedGitLocation.filepath);
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { DirectoryPreparer } from './dir';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { DirectoryPreparer } from './dir';
|
||||
import { checkoutGitRepository } from '../../helpers';
|
||||
|
||||
function normalizePath(path: string) {
|
||||
@@ -44,9 +45,11 @@ const createMockEntity = (annotations: {}) => {
|
||||
};
|
||||
};
|
||||
|
||||
const mockConfig = new ConfigReader({});
|
||||
|
||||
describe('directory preparer', () => {
|
||||
it('should merge managed-by-location and techdocs-ref when techdocs-ref is relative', async () => {
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/managed-by-location':
|
||||
@@ -60,7 +63,7 @@ describe('directory preparer', () => {
|
||||
});
|
||||
|
||||
it('should merge managed-by-location and techdocs-ref when techdocs-ref is absolute', async () => {
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/managed-by-location':
|
||||
@@ -74,7 +77,7 @@ describe('directory preparer', () => {
|
||||
});
|
||||
|
||||
it('should merge managed-by-location and techdocs-ref when managed-by-location is a git repository', async () => {
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/managed-by-location':
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import { PreparerBase } from './types';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import path from 'path';
|
||||
import { parseReferenceAnnotation, checkoutGitRepository } from '../../helpers';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
@@ -22,9 +23,11 @@ import parseGitUrl from 'git-url-parse';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
export class DirectoryPreparer implements PreparerBase {
|
||||
private readonly config: Config;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger) {
|
||||
constructor(config: Config, logger: Logger) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -43,7 +46,11 @@ export class DirectoryPreparer implements PreparerBase {
|
||||
case 'url':
|
||||
case 'azure/api': {
|
||||
const parsedGitLocation = parseGitUrl(target);
|
||||
const repoLocation = await checkoutGitRepository(target, this.logger);
|
||||
const repoLocation = await checkoutGitRepository(
|
||||
target,
|
||||
this.config,
|
||||
this.logger,
|
||||
);
|
||||
|
||||
return path.dirname(
|
||||
path.join(repoLocation, parsedGitLocation.filepath),
|
||||
|
||||
@@ -30,18 +30,15 @@ export class Preparers implements PreparerBuilder {
|
||||
private preparerMap = new Map<RemoteProtocol, PreparerBase>();
|
||||
|
||||
static async fromConfig(
|
||||
// @ts-ignore
|
||||
// Config not used now, but will be used in urlPreparer when it starts using
|
||||
// @backstage/integration to get the tokens for providers.
|
||||
config: Config,
|
||||
{ logger, reader }: factoryOptions,
|
||||
): Promise<PreparerBuilder> {
|
||||
const preparers = new Preparers();
|
||||
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(config, logger);
|
||||
preparers.register('dir', directoryPreparer);
|
||||
|
||||
const commonGitPreparer = new CommonGitPreparer(logger);
|
||||
const commonGitPreparer = new CommonGitPreparer(config, logger);
|
||||
preparers.register('github', commonGitPreparer);
|
||||
preparers.register('gitlab', commonGitPreparer);
|
||||
preparers.register('azure/api', commonGitPreparer);
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Logger } from 'winston';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { PreparerBase } from './types';
|
||||
import { getDocFilesFromRepository } from '../../helpers';
|
||||
import { Logger } from 'winston';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
|
||||
export class UrlPreparer implements PreparerBase {
|
||||
private readonly logger: Logger;
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import mockFs from 'mock-fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
|
||||
|
||||
describe('getHeadersForFileExtension', () => {
|
||||
@@ -39,9 +41,11 @@ describe('getHeadersForFileExtension', () => {
|
||||
});
|
||||
|
||||
describe('getFileTreeRecursively', () => {
|
||||
const root = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'/rootDir': {
|
||||
[root]: {
|
||||
file1: '',
|
||||
subDirA: {
|
||||
file2: '',
|
||||
@@ -57,9 +61,9 @@ describe('getFileTreeRecursively', () => {
|
||||
});
|
||||
|
||||
it('returns complete file tree of a path', async () => {
|
||||
const fileList = await getFileTreeRecursively('/rootDir');
|
||||
const fileList = await getFileTreeRecursively(root);
|
||||
expect(fileList.length).toBe(2);
|
||||
expect(fileList).toContain('/rootDir/file1');
|
||||
expect(fileList).toContain('/rootDir/subDirA/file2');
|
||||
expect(fileList).toContain(path.resolve(root, 'file1'));
|
||||
expect(fileList).toContain(path.resolve(root, 'subDirA/file2'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/plugin-api-docs
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9161531b2: Link register API to catalog-import plugin
|
||||
- Updated dependencies [a08c32ced]
|
||||
- Updated dependencies [7e0b8cac5]
|
||||
- Updated dependencies [87c0c53c2]
|
||||
- @backstage/core@0.4.3
|
||||
- @backstage/plugin-catalog@0.2.9
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-api-docs",
|
||||
"version": "0.4.1",
|
||||
"version": "0.4.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -30,8 +30,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.9",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@kyma-project/asyncapi-react": "^0.14.2",
|
||||
"@material-icons/font": "^1.0.2",
|
||||
@@ -49,8 +49,8 @@
|
||||
"swagger-ui-react": "^3.37.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/plugin-auth-backend
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cc046682e: fix bug in token expiration date
|
||||
|
||||
## 0.2.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-auth-backend",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -64,7 +64,7 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.2",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@types/body-parser": "^1.19.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/express-session": "^1.17.2",
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/plugin-catalog-backend": "^0.5.0",
|
||||
"@backstage/integration": "^0.1.4",
|
||||
@@ -50,8 +50,8 @@
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @backstage/plugin-catalog
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7e0b8cac5: Add `CatalogIndexPage` and `CatalogEntityPage`, two new extensions that replace the existing `Router` component.
|
||||
|
||||
Add `EntityLayout` to replace `EntityPageLayout`, using children instead of an element property, and allowing for collection of all `RouteRef` mount points used within tabs.
|
||||
|
||||
Add `EntitySwitch` to be used to select components based on entity data, along with accompanying `isKind`, `isNamespace`, and `isComponentType` filters.
|
||||
|
||||
- 87c0c53c2: Add new `EntityProvider` component, which can be used to provide an entity for the `useEntity` hook.
|
||||
- Updated dependencies [a08c32ced]
|
||||
- Updated dependencies [359f9d2d8]
|
||||
- @backstage/core@0.4.3
|
||||
- @backstage/plugin-techdocs@0.5.2
|
||||
|
||||
## 0.2.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog",
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,9 +32,9 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-client": "^0.3.4",
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-scaffolder": "^0.3.5",
|
||||
"@backstage/plugin-techdocs": "^0.5.1",
|
||||
"@backstage/plugin-techdocs": "^0.5.2",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -52,8 +52,8 @@
|
||||
"swr": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@microsoft/microsoft-graph-types": "^1.25.0",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -50,8 +50,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -49,8 +49,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-cost-insights
|
||||
|
||||
## 0.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3fca9adb9: Fix links in sample instructions
|
||||
- Updated dependencies [a08c32ced]
|
||||
- @backstage/core@0.4.3
|
||||
|
||||
## 0.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-cost-insights",
|
||||
"version": "0.5.3",
|
||||
"version": "0.5.4",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/test-utils": "^0.1.5",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -56,8 +56,8 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
+10
-10
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
import { CodeSnippet } from '@backstage/core';
|
||||
import { CodeSnippet, Link } from '@backstage/core';
|
||||
import { AlertInstructionsLayout } from '../AlertInstructionsLayout';
|
||||
|
||||
export const LabelDataflowInstructionsPage = () => {
|
||||
@@ -31,10 +31,10 @@ export const LabelDataflowInstructionsPage = () => {
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
In Cloud Dataflow, labels can be added to a job either programmatically
|
||||
or via the command-line when launching a job. Note that GCP has
|
||||
<a href="https://cloud.google.com/compute/docs/labeling-resources#restrictions">
|
||||
or via the command-line when launching a job. Note that GCP has{' '}
|
||||
<Link to="https://cloud.google.com/compute/docs/labeling-resources#restrictions">
|
||||
restrictions
|
||||
</a>{' '}
|
||||
</Link>{' '}
|
||||
on the length and characters that can be used in labels.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
@@ -46,9 +46,9 @@ export const LabelDataflowInstructionsPage = () => {
|
||||
<Typography variant="h3">DataflowPipelineOptions</Typography>
|
||||
<Typography paragraph>
|
||||
Dataflow jobs using Beam's{' '}
|
||||
<a href="https://beam.apache.org/releases/javadoc/2.3.0/org/apache/beam/runners/dataflow/options/DataflowPipelineOptions.html">
|
||||
<Link to="https://beam.apache.org/releases/javadoc/2.3.0/org/apache/beam/runners/dataflow/options/DataflowPipelineOptions.html">
|
||||
DataflowPipelineOptions
|
||||
</a>{' '}
|
||||
</Link>{' '}
|
||||
directly can use the <b>setLabels</b> function to add one or more
|
||||
labels:
|
||||
<CodeSnippet
|
||||
@@ -79,13 +79,13 @@ sc.optionsAs[DataflowPipelineOptions].setLabels(Map("job-id" -> "my-dataflow-job
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
For more information on specifying options, see the{' '}
|
||||
<a href="https://cloud.google.com/dataflow/docs/guides/specifying-exec-params">
|
||||
<Link to="https://cloud.google.com/dataflow/docs/guides/specifying-exec-params">
|
||||
Dataflow documentation
|
||||
</a>{' '}
|
||||
</Link>{' '}
|
||||
or{' '}
|
||||
<a href="https://spotify.github.io/scio/api/com/spotify/scio/ScioContext.html">
|
||||
<Link to="https://spotify.github.io/scio/api/com/spotify/scio/ScioContext.html">
|
||||
Scio Scaladoc
|
||||
</a>
|
||||
</Link>
|
||||
.
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
+2
-2
@@ -135,7 +135,7 @@ export const ProjectGrowthInstructionsPage = () => {
|
||||
<Typography paragraph>
|
||||
Next, evaluate whether the growth is significant. This helps avoid
|
||||
premature optimization, where cost in engineering time is more than
|
||||
would be saved from the optimization over a reasonable timeframe.
|
||||
would be saved from the optimization over a reasonable time frame.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
We recommend reframing the cost growth itself in terms of engineering
|
||||
@@ -205,7 +205,7 @@ export const ProjectGrowthInstructionsPage = () => {
|
||||
Is the workload using cloud resources efficiently? For compute
|
||||
resources, do the utilization metrics look reasonable? Autoscaling
|
||||
infrastructure, such as Kubernetes, can run workloads more efficiently
|
||||
without comprimising reliability.
|
||||
without compromising reliability.
|
||||
</Typography>
|
||||
<Typography variant="h5">Lifecycle</Typography>
|
||||
<Typography paragraph>
|
||||
|
||||
@@ -24,7 +24,7 @@ export const DEFAULT_DURATION = Duration.P30D;
|
||||
* Derive the start date of a given period, assuming two repeating intervals.
|
||||
*
|
||||
* @param duration see comment on Duration enum
|
||||
* @param endDate from CostInsightsApi.getLastCompleteBillingDate + 1 day
|
||||
* @param exclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate + 1 day
|
||||
*/
|
||||
export function inclusiveStartDateOf(
|
||||
duration: Duration,
|
||||
|
||||
@@ -234,14 +234,24 @@ export function aggregationFor(
|
||||
'day',
|
||||
);
|
||||
|
||||
function nextDelta(): number {
|
||||
const varianceFromBaseline = 0.15;
|
||||
// Let's give positive vibes in trendlines - higher change for positive delta with >0.5 value
|
||||
const positiveTrendChance = 0.55;
|
||||
const normalization = positiveTrendChance - 1;
|
||||
return baseline * (Math.random() + normalization) * varianceFromBaseline;
|
||||
}
|
||||
|
||||
return [...Array(days).keys()].reduce(
|
||||
(values: DateAggregation[], i: number): DateAggregation[] => {
|
||||
const last = values.length ? values[values.length - 1].amount : baseline;
|
||||
const date = dayjs(inclusiveStartDateOf(duration, endDate))
|
||||
.add(i, 'day')
|
||||
.format(DEFAULT_DATE_FORMAT);
|
||||
const amount = Math.max(0, last + nextDelta());
|
||||
values.push({
|
||||
date: dayjs(inclusiveStartDateOf(duration, endDate))
|
||||
.add(i, 'day')
|
||||
.format(DEFAULT_DATE_FORMAT),
|
||||
amount: Math.max(0, last + (baseline / 20) * (Math.random() * 2 - 1)),
|
||||
date: date,
|
||||
amount: amount,
|
||||
});
|
||||
return values;
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"start": "backstage-cli plugin:serve"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -42,8 +42,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# @backstage/plugin-fossa
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7afdfef98: Bump dependency versions of @backstage/core, cli and test-utils
|
||||
- Updated dependencies [a08c32ced]
|
||||
- @backstage/core@0.4.3
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-fossa",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -43,8 +43,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -41,8 +41,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/core-api": "^0.2.7",
|
||||
"@backstage/plugin-catalog": "^0.2.8",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
@@ -51,8 +51,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -42,8 +42,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -43,8 +43,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -46,8 +46,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-kubernetes-backend": "^0.2.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@kubernetes/client-node": "^0.12.1",
|
||||
@@ -46,8 +46,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/core-api": "^0.2.6",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
@@ -47,8 +47,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -41,8 +41,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -33,8 +33,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -44,8 +44,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/plugin-register-component
|
||||
|
||||
## 0.2.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1517876fd: Register component plugin is deprecated in favor of @backstage/plugin-catalog-import
|
||||
- Updated dependencies [a08c32ced]
|
||||
- Updated dependencies [7e0b8cac5]
|
||||
- Updated dependencies [87c0c53c2]
|
||||
- @backstage/core@0.4.3
|
||||
- @backstage/plugin-catalog@0.2.9
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-register-component",
|
||||
"version": "0.2.5",
|
||||
"version": "0.2.6",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,8 +31,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.9",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -45,8 +45,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.6.0",
|
||||
"@backstage/core": "^0.4.2",
|
||||
"@backstage/core": "^0.4.3",
|
||||
"@backstage/plugin-catalog": "^0.2.7",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -47,8 +47,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@backstage/dev-utils": "^0.1.6",
|
||||
"@backstage/cli": "^0.4.4",
|
||||
"@backstage/dev-utils": "^0.1.7",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"command-exists-promise": "^2.0.2",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"dockerode": "^3.2.1",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
@@ -49,20 +50,18 @@
|
||||
"git-url-parse": "^11.4.3",
|
||||
"globby": "^11.0.0",
|
||||
"helmet": "^4.0.0",
|
||||
"isomorphic-git": "^1.8.0",
|
||||
"jsonschema": "^1.2.6",
|
||||
"morgan": "^1.10.0",
|
||||
"nodegit": "0.27.0",
|
||||
"uuid": "^8.2.0",
|
||||
"winston": "^3.2.1",
|
||||
"yaml": "^1.10.0",
|
||||
"cross-fetch": "^3.0.6"
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.4.3",
|
||||
"@octokit/types": "^5.4.1",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/nodegit": "0.26.11",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"supertest": "^4.0.2",
|
||||
"yaml": "^1.10.0"
|
||||
|
||||
@@ -14,11 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const mocks = {
|
||||
Clone: { clone: jest.fn() },
|
||||
CheckoutOptions: jest.fn(() => {}),
|
||||
};
|
||||
jest.doMock('nodegit', () => mocks);
|
||||
jest.doMock('fs-extra', () => ({
|
||||
promises: {
|
||||
mkdtemp: jest.fn(dir => `${dir}-static`),
|
||||
@@ -30,10 +25,16 @@ import {
|
||||
TemplateEntityV1alpha1,
|
||||
LOCATION_ANNOTATION,
|
||||
} from '@backstage/catalog-model';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { getVoidLogger, Git } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
describe('AzurePreparer', () => {
|
||||
const mockGitClient = {
|
||||
clone: jest.fn(),
|
||||
};
|
||||
|
||||
jest.spyOn(Git, 'fromAuth').mockReturnValue(mockGitClient as any);
|
||||
|
||||
let mockEntity: TemplateEntityV1alpha1;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -77,18 +78,7 @@ describe('AzurePreparer', () => {
|
||||
};
|
||||
});
|
||||
|
||||
it('calls the clone command with the correct arguments for a repository', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}));
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://dev.azure.com/backstage-org/backstage-project/_git/template-repo',
|
||||
expect.any(String),
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the clone command with the correct arguments if an access token is provided for a repository', async () => {
|
||||
it('initializes git client with the correct arguments if an access token is provided for a repository', async () => {
|
||||
const preparer = new AzurePreparer(
|
||||
new ConfigReader({
|
||||
scaffolder: {
|
||||
@@ -100,36 +90,44 @@ describe('AzurePreparer', () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
const logger = getVoidLogger();
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
username: 'notempty',
|
||||
password: 'fake-token',
|
||||
logger,
|
||||
});
|
||||
});
|
||||
it('calls the clone command with the correct arguments for a repository', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}));
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://dev.azure.com/backstage-org/backstage-project/_git/template-repo',
|
||||
expect.any(String),
|
||||
{
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: expect.anything(),
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url:
|
||||
'https://dev.azure.com/backstage-org/backstage-project/_git/template-repo',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('calls the clone command with the correct arguments for a repository when no path is provided', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}));
|
||||
delete mockEntity.spec.path;
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://dev.azure.com/backstage-org/backstage-project/_git/template-repo',
|
||||
expect.any(String),
|
||||
{},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url:
|
||||
'https://dev.azure.com/backstage-org/backstage-project/_git/template-repo',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('return the temp directory with the path to the folder if it is specified', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}));
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
@@ -142,6 +140,7 @@ describe('AzurePreparer', () => {
|
||||
it('return the working directory with the path to the folder if it is specified', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}));
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
logger: getVoidLogger(),
|
||||
workingDirectory: '/workDir',
|
||||
|
||||
@@ -18,10 +18,9 @@ import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { parseLocationAnnotation } from '../helpers';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
import { InputError, Git } from '@backstage/backend-common';
|
||||
import { PreparerBase, PreparerOptions } from './types';
|
||||
import GitUriParser from 'git-url-parse';
|
||||
import { Clone, Cred } from 'nodegit';
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
export class AzurePreparer implements PreparerBase {
|
||||
@@ -38,6 +37,7 @@ export class AzurePreparer implements PreparerBase {
|
||||
): Promise<string> {
|
||||
const { protocol, location } = parseLocationAnnotation(template);
|
||||
const workingDirectory = opts?.workingDirectory ?? os.tmpdir();
|
||||
const { logger } = opts;
|
||||
|
||||
if (!['azure/api', 'url'].includes(protocol)) {
|
||||
throw new InputError(
|
||||
@@ -57,19 +57,20 @@ export class AzurePreparer implements PreparerBase {
|
||||
template.spec.path ?? '.',
|
||||
);
|
||||
|
||||
const options = this.privateToken
|
||||
? {
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: () =>
|
||||
// Username can anything but the empty string according to: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page#use-a-pat
|
||||
Cred.userpassPlaintextNew('notempty', this.privateToken),
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
// Username can be anything but the empty string according to:
|
||||
// https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page#use-a-pat
|
||||
const git = this.privateToken
|
||||
? Git.fromAuth({
|
||||
password: this.privateToken,
|
||||
username: 'notempty',
|
||||
logger,
|
||||
})
|
||||
: Git.fromAuth({ logger });
|
||||
|
||||
await Clone.clone(repositoryCheckoutUrl, tempDir, options);
|
||||
await git.clone({
|
||||
url: repositoryCheckoutUrl,
|
||||
dir: tempDir,
|
||||
});
|
||||
|
||||
return path.resolve(tempDir, templateDirectory);
|
||||
}
|
||||
|
||||
@@ -14,11 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const mocks = {
|
||||
Clone: { clone: jest.fn() },
|
||||
CheckoutOptions: jest.fn(() => {}),
|
||||
};
|
||||
jest.doMock('nodegit', () => mocks);
|
||||
jest.doMock('fs-extra', () => ({
|
||||
promises: {
|
||||
mkdtemp: jest.fn(dir => `${dir}-static`),
|
||||
@@ -30,10 +25,16 @@ import {
|
||||
TemplateEntityV1alpha1,
|
||||
LOCATION_ANNOTATION,
|
||||
} from '@backstage/catalog-model';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { getVoidLogger, Git } from '@backstage/backend-common';
|
||||
|
||||
describe('GitHubPreparer', () => {
|
||||
let mockEntity: TemplateEntityV1alpha1;
|
||||
const mockGitClient = {
|
||||
clone: jest.fn(),
|
||||
};
|
||||
|
||||
jest.spyOn(Git, 'fromAuth').mockReturnValue(mockGitClient as any);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockEntity = {
|
||||
@@ -77,28 +78,24 @@ describe('GitHubPreparer', () => {
|
||||
});
|
||||
it('calls the clone command with the correct arguments for a repository', async () => {
|
||||
const preparer = new GithubPreparer();
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
checkoutBranch: 'master',
|
||||
},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
it('calls the clone command with the correct arguments for a repository when no path is provided', async () => {
|
||||
const preparer = new GithubPreparer();
|
||||
delete mockEntity.spec.path;
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
checkoutBranch: 'master',
|
||||
},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('return the temp directory with the path to the folder if it is specified', async () => {
|
||||
@@ -128,19 +125,14 @@ describe('GitHubPreparer', () => {
|
||||
|
||||
it('calls the clone command with the token when provided', async () => {
|
||||
const preparer = new GithubPreparer({ token: 'abc' });
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
checkoutBranch: 'master',
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: expect.any(Function),
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
const logger = getVoidLogger();
|
||||
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
username: 'abc',
|
||||
password: 'x-oauth-basic',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,10 +18,9 @@ import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { parseLocationAnnotation } from '../helpers';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
import { InputError, Git } from '@backstage/backend-common';
|
||||
import { PreparerBase, PreparerOptions } from './types';
|
||||
import GitUriParser from 'git-url-parse';
|
||||
import { Clone, CloneOptions, Cred } from 'nodegit';
|
||||
|
||||
export class GithubPreparer implements PreparerBase {
|
||||
token?: string;
|
||||
@@ -36,7 +35,7 @@ export class GithubPreparer implements PreparerBase {
|
||||
): Promise<string> {
|
||||
const { protocol, location } = parseLocationAnnotation(template);
|
||||
const workingDirectory = opts?.workingDirectory ?? os.tmpdir();
|
||||
const { token } = this;
|
||||
const { logger } = opts;
|
||||
|
||||
if (!['github', 'url'].includes(protocol)) {
|
||||
throw new InputError(
|
||||
@@ -56,25 +55,21 @@ export class GithubPreparer implements PreparerBase {
|
||||
template.spec.path ?? '.',
|
||||
);
|
||||
|
||||
let cloneOptions: CloneOptions = {
|
||||
checkoutBranch: parsedGitLocation.ref,
|
||||
};
|
||||
const checkoutLocation = path.resolve(tempDir, templateDirectory);
|
||||
|
||||
if (token) {
|
||||
cloneOptions = {
|
||||
...cloneOptions,
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials() {
|
||||
return Cred.userpassPlaintextNew(token, 'x-oauth-basic');
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const git = this.token
|
||||
? Git.fromAuth({
|
||||
username: this.token,
|
||||
password: 'x-oauth-basic',
|
||||
logger,
|
||||
})
|
||||
: Git.fromAuth({ logger });
|
||||
|
||||
await Clone.clone(repositoryCheckoutUrl, tempDir, cloneOptions);
|
||||
await git.clone({
|
||||
url: repositoryCheckoutUrl,
|
||||
dir: tempDir,
|
||||
});
|
||||
|
||||
return path.resolve(tempDir, templateDirectory);
|
||||
return checkoutLocation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const mocks = {
|
||||
Clone: { clone: jest.fn() },
|
||||
CheckoutOptions: jest.fn(() => {}),
|
||||
};
|
||||
jest.doMock('nodegit', () => mocks);
|
||||
jest.doMock('fs-extra', () => ({
|
||||
promises: {
|
||||
mkdtemp: jest.fn(dir => `${dir}-static`),
|
||||
@@ -30,7 +25,7 @@ import {
|
||||
LOCATION_ANNOTATION,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { getVoidLogger, Git } from '@backstage/backend-common';
|
||||
|
||||
const mockEntityWithProtocol = (protocol: string): TemplateEntityV1alpha1 => ({
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
@@ -72,6 +67,12 @@ const mockEntityWithProtocol = (protocol: string): TemplateEntityV1alpha1 => ({
|
||||
|
||||
describe('GitLabPreparer', () => {
|
||||
let mockEntity: TemplateEntityV1alpha1;
|
||||
const mockGitClient = {
|
||||
clone: jest.fn(),
|
||||
};
|
||||
|
||||
jest.spyOn(Git, 'fromAuth').mockReturnValue(mockGitClient as any);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -80,13 +81,13 @@ describe('GitLabPreparer', () => {
|
||||
it(`calls the clone command with the correct arguments for a repository using the ${protocol} protocol`, async () => {
|
||||
const preparer = new GitlabPreparer(new ConfigReader({}));
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it(`calls the clone command with the correct arguments if an access token is provided in integrations for a repository using the ${protocol} protocol`, async () => {
|
||||
@@ -103,19 +104,15 @@ describe('GitLabPreparer', () => {
|
||||
}),
|
||||
);
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: expect.anything(),
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
const logger = getVoidLogger();
|
||||
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
username: 'oauth2',
|
||||
password: 'fake-token',
|
||||
});
|
||||
});
|
||||
|
||||
it(`calls the clone command with the correct arguments if an access token is provided in scaffolder for a repository using the ${protocol} protocol`, async () => {
|
||||
@@ -127,32 +124,28 @@ describe('GitLabPreparer', () => {
|
||||
}),
|
||||
);
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: expect.anything(),
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
const logger = getVoidLogger();
|
||||
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
username: 'oauth2',
|
||||
password: 'fake-token',
|
||||
});
|
||||
});
|
||||
|
||||
it(`calls the clone command with the correct arguments for a repository when no path is provided using the ${protocol} protocol`, async () => {
|
||||
const preparer = new GitlabPreparer(new ConfigReader({}));
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
delete mockEntity.spec.path;
|
||||
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mocks.Clone.clone).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
expect.any(String),
|
||||
{},
|
||||
);
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
dir: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it(`return the temp directory with the path to the folder if it is specified using the ${protocol} protocol`, async () => {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
import { InputError, Git } from '@backstage/backend-common';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
} from '@backstage/integration';
|
||||
import fs from 'fs-extra';
|
||||
import GitUriParser from 'git-url-parse';
|
||||
import { Clone, Cred } from 'nodegit';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { parseLocationAnnotation } from '../helpers';
|
||||
@@ -46,6 +45,7 @@ export class GitlabPreparer implements PreparerBase {
|
||||
opts: PreparerOptions,
|
||||
): Promise<string> {
|
||||
const { protocol, location } = parseLocationAnnotation(template);
|
||||
const { logger } = opts;
|
||||
const workingDirectory = opts?.workingDirectory ?? os.tmpdir();
|
||||
|
||||
if (!['gitlab', 'gitlab/api', 'url'].includes(protocol)) {
|
||||
@@ -67,17 +67,18 @@ export class GitlabPreparer implements PreparerBase {
|
||||
);
|
||||
|
||||
const token = this.getToken(parsedGitLocation.resource);
|
||||
const options = token
|
||||
? {
|
||||
fetchOpts: {
|
||||
callbacks: {
|
||||
credentials: () => Cred.userpassPlaintextNew('oauth2', token),
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
const git = token
|
||||
? Git.fromAuth({
|
||||
password: token,
|
||||
username: 'oauth2',
|
||||
logger,
|
||||
})
|
||||
: Git.fromAuth({ logger });
|
||||
|
||||
await Clone.clone(repositoryCheckoutUrl, tempDir, options);
|
||||
await git.clone({
|
||||
url: repositoryCheckoutUrl,
|
||||
dir: tempDir,
|
||||
});
|
||||
|
||||
return path.resolve(tempDir, templateDirectory);
|
||||
}
|
||||
|
||||
@@ -13,17 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
jest.mock('nodegit');
|
||||
jest.mock('azure-devops-node-api/GitApi');
|
||||
jest.mock('azure-devops-node-api/interfaces/GitInterfaces');
|
||||
jest.mock('./helpers', () => ({
|
||||
pushToRemoteUserPass: jest.fn(),
|
||||
}));
|
||||
jest.mock('./helpers');
|
||||
|
||||
import { AzurePublisher } from './azure';
|
||||
import { GitApi } from 'azure-devops-node-api/GitApi';
|
||||
import { pushToRemoteUserPass } from './helpers';
|
||||
import * as helpers from './helpers';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
|
||||
const { mockGitApi } = require('azure-devops-node-api/GitApi') as {
|
||||
mockGitApi: {
|
||||
@@ -33,6 +28,7 @@ const { mockGitApi } = require('azure-devops-node-api/GitApi') as {
|
||||
|
||||
describe('Azure Publisher', () => {
|
||||
const publisher = new AzurePublisher(new GitApi('', []), 'fake-token');
|
||||
const logger = getVoidLogger();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -50,6 +46,7 @@ describe('Azure Publisher', () => {
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -63,12 +60,12 @@ describe('Azure Publisher', () => {
|
||||
},
|
||||
'project',
|
||||
);
|
||||
expect(pushToRemoteUserPass).toHaveBeenCalledWith(
|
||||
'/tmp/test',
|
||||
'https://dev.azure.com/organization/project/_git/repo',
|
||||
'notempty',
|
||||
'fake-token',
|
||||
);
|
||||
expect(helpers.initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: '/tmp/test',
|
||||
remoteUrl: 'https://dev.azure.com/organization/project/_git/repo',
|
||||
auth: { username: 'notempty', password: 'fake-token' },
|
||||
logger,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user