Merge pull request #3805 from backstage/blam/isomorphic-git
Remove the NodeGit Dependency in favour of isomorphic-git
This commit is contained in:
@@ -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
|
||||
@@ -134,6 +134,7 @@ neuro
|
||||
newrelic
|
||||
nginx
|
||||
Niklas
|
||||
nodegit
|
||||
nohoist
|
||||
nonces
|
||||
npm
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
@@ -51,7 +51,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -62,7 +61,6 @@
|
||||
"@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": {
|
||||
|
||||
@@ -17,9 +17,8 @@
|
||||
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 } from '@backstage/backend-common';
|
||||
import { InputError, UrlReader, Git } from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getDefaultBranch } from './default-branch';
|
||||
@@ -27,10 +26,6 @@ import { getGitRepoType, getTokenForGitRepo } from './git-auth';
|
||||
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;
|
||||
@@ -128,17 +123,61 @@ export const checkoutGitRepository = async (
|
||||
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(
|
||||
@@ -148,26 +187,10 @@ 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;
|
||||
};
|
||||
@@ -183,10 +206,11 @@ export const getLastCommitTimestamp = async (
|
||||
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 (
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { RequiredTemplateValues } from '../templater';
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { RemoteProtocol } from '../types';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
/**
|
||||
* Publisher is in charge of taking a folder created by
|
||||
@@ -34,6 +35,7 @@ export type PublisherBase = {
|
||||
|
||||
export type PublisherOptions = {
|
||||
values: RequiredTemplateValues & Record<string, JsonValue>;
|
||||
logger: Logger;
|
||||
directory: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ export async function createRouter(
|
||||
const result = await publisher.publish({
|
||||
values: ctx.values,
|
||||
directory: ctx.resultDir,
|
||||
logger: ctx.logger,
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -4348,11 +4348,6 @@
|
||||
resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
||||
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
|
||||
|
||||
"@sindresorhus/is@^2.0.0":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1"
|
||||
integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==
|
||||
|
||||
"@sindresorhus/is@^3.1.1":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-3.1.2.tgz#548650de521b344e3781fbdb0ece4aa6f729afb8"
|
||||
@@ -5110,7 +5105,7 @@
|
||||
dependencies:
|
||||
defer-to-connect "^1.0.1"
|
||||
|
||||
"@szmarczak/http-timer@^4.0.0", "@szmarczak/http-timer@^4.0.5":
|
||||
"@szmarczak/http-timer@^4.0.5":
|
||||
version "4.0.5"
|
||||
resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152"
|
||||
integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==
|
||||
@@ -5822,7 +5817,7 @@
|
||||
resolved "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
|
||||
integrity sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==
|
||||
|
||||
"@types/keyv@*", "@types/keyv@^3.1.1":
|
||||
"@types/keyv@*":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
|
||||
integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==
|
||||
@@ -5977,20 +5972,6 @@
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-13.13.15.tgz#fe1cc3aa465a3ea6858b793fd380b66c39919766"
|
||||
integrity sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw==
|
||||
|
||||
"@types/nodegit@0.26.11":
|
||||
version "0.26.11"
|
||||
resolved "https://registry.npmjs.org/@types/nodegit/-/nodegit-0.26.11.tgz#0cbc5e929f23e5ffc536920e3a887e0b3f46d1a4"
|
||||
integrity sha512-BGrY9F8lBtfU+Ne1Pjb9k/PUsfSCUAqPXgKkTkM6mc5213H5VAoM12zG/sz/cr3jI3AXcngNbAYWlqSrXsWJug==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/nodegit@^0.26.12":
|
||||
version "0.26.12"
|
||||
resolved "https://registry.npmjs.org/@types/nodegit/-/nodegit-0.26.12.tgz#93afb4cb85d3a48d392c3232699c9c07d8251a36"
|
||||
integrity sha512-4YpeTImFZNJ1cve4lEueHFVS8rAs8XpZqlmx+Bm9bMc+XMiCrcwaUf6peN7pod7Rl3esVlGP1zdBB7Z12eMVAA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.0"
|
||||
resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
||||
@@ -7643,6 +7624,11 @@ async-limiter@~1.0.0:
|
||||
resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
||||
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||
|
||||
async-lock@^1.1.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.npmjs.org/async-lock/-/async-lock-1.2.4.tgz#80d0d612383045dd0c30eb5aad08510c1397cb91"
|
||||
integrity sha512-UBQJC2pbeyGutIfYmErGc9RaJYnpZ1FHaxuKwb0ahvGiiCkPUf3p67Io+YLPmmv3RHY+mF6JEtNW8FlHsraAaA==
|
||||
|
||||
async-retry@^1.2.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55"
|
||||
@@ -8280,14 +8266,6 @@ bindings@^1.5.0:
|
||||
dependencies:
|
||||
file-uri-to-path "1.0.0"
|
||||
|
||||
bl@^1.0.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||
integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
|
||||
dependencies:
|
||||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
bl@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489"
|
||||
@@ -8546,19 +8524,6 @@ btoa@^1.2.1:
|
||||
resolved "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73"
|
||||
integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==
|
||||
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
|
||||
|
||||
buffer-alloc@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
|
||||
dependencies:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
@@ -8569,11 +8534,6 @@ buffer-equal-constant-time@1.0.1:
|
||||
resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
|
||||
|
||||
buffer-fill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
|
||||
|
||||
buffer-from@1.x, buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
@@ -8742,14 +8702,6 @@ cache-base@^1.0.1:
|
||||
union-value "^1.0.0"
|
||||
unset-value "^1.0.0"
|
||||
|
||||
cacheable-lookup@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz#87be64a18b925234875e10a9bb1ebca4adce6b38"
|
||||
integrity sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==
|
||||
dependencies:
|
||||
"@types/keyv" "^3.1.1"
|
||||
keyv "^4.0.0"
|
||||
|
||||
cacheable-lookup@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz#049fdc59dffdd4fc285e8f4f82936591bd59fec3"
|
||||
@@ -9057,7 +9009,7 @@ chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.3.1, chokidar@^3.4.1, chokidar@^3.
|
||||
optionalDependencies:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.2:
|
||||
chownr@^1.1.1, chownr@^1.1.2:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||
@@ -9121,6 +9073,11 @@ clean-css@^4.2.3:
|
||||
dependencies:
|
||||
source-map "~0.6.0"
|
||||
|
||||
clean-git-ref@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz#dcc0ca093b90e527e67adb5a5e55b1af6816dcd9"
|
||||
integrity sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==
|
||||
|
||||
clean-stack@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
||||
@@ -10716,13 +10673,6 @@ decompress-response@^4.2.0:
|
||||
dependencies:
|
||||
mimic-response "^2.0.0"
|
||||
|
||||
decompress-response@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz#7849396e80e3d1eba8cb2f75ef4930f76461cb0f"
|
||||
integrity sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==
|
||||
dependencies:
|
||||
mimic-response "^2.0.0"
|
||||
|
||||
decompress-response@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
|
||||
@@ -10981,6 +10931,11 @@ diff-sequences@^26.6.2:
|
||||
resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
|
||||
integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==
|
||||
|
||||
diff3@0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz#d4e5c3a4cdf4e5fe1211ab42e693fcb4321580fc"
|
||||
integrity sha1-1OXDpM305f4SEatC5pP8tDIVgPw=
|
||||
|
||||
diff@^4.0.1, diff@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
@@ -12866,7 +12821,7 @@ fs-extra@^0.30.0:
|
||||
path-is-absolute "^1.0.0"
|
||||
rimraf "^2.2.8"
|
||||
|
||||
fs-extra@^7.0.0, fs-extra@^7.0.1:
|
||||
fs-extra@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
|
||||
integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
|
||||
@@ -13438,27 +13393,6 @@ google-p12-pem@^3.0.3:
|
||||
dependencies:
|
||||
node-forge "^0.10.0"
|
||||
|
||||
got@^10.7.0:
|
||||
version "10.7.0"
|
||||
resolved "https://registry.npmjs.org/got/-/got-10.7.0.tgz#62889dbcd6cca32cd6a154cc2d0c6895121d091f"
|
||||
integrity sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==
|
||||
dependencies:
|
||||
"@sindresorhus/is" "^2.0.0"
|
||||
"@szmarczak/http-timer" "^4.0.0"
|
||||
"@types/cacheable-request" "^6.0.1"
|
||||
cacheable-lookup "^2.0.0"
|
||||
cacheable-request "^7.0.1"
|
||||
decompress-response "^5.0.0"
|
||||
duplexer3 "^0.1.4"
|
||||
get-stream "^5.0.0"
|
||||
lowercase-keys "^2.0.0"
|
||||
mimic-response "^2.1.0"
|
||||
p-cancelable "^2.0.0"
|
||||
p-event "^4.0.0"
|
||||
responselike "^2.0.0"
|
||||
to-readable-stream "^2.0.0"
|
||||
type-fest "^0.10.0"
|
||||
|
||||
got@^11.5.2:
|
||||
version "11.6.0"
|
||||
resolved "https://registry.npmjs.org/got/-/got-11.6.0.tgz#4978c78f3cbc3a45ee95381f8bb6efd1db1f4638"
|
||||
@@ -15201,6 +15135,23 @@ isomorphic-form-data@~2.0.0:
|
||||
dependencies:
|
||||
form-data "^2.3.2"
|
||||
|
||||
isomorphic-git@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.8.0.tgz#50440650a64706a321cbea1af955c1cf1110b238"
|
||||
integrity sha512-TWJvQh+++eFrEG0IFS/jLhMwsBoCOX1/Dsw9q8no59Mp1K0jEjSHXFWv2P04PwkxcIpePkXVBI5YFcFT2nkuQg==
|
||||
dependencies:
|
||||
async-lock "^1.1.0"
|
||||
clean-git-ref "^2.0.1"
|
||||
crc-32 "^1.2.0"
|
||||
diff3 "0.0.3"
|
||||
ignore "^5.1.4"
|
||||
minimisted "^2.0.0"
|
||||
pako "^1.0.10"
|
||||
pify "^4.0.1"
|
||||
readable-stream "^3.4.0"
|
||||
sha.js "^2.4.9"
|
||||
simple-get "^3.0.2"
|
||||
|
||||
isomorphic-ws@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
|
||||
@@ -15944,7 +15895,7 @@ json3@^3.3.2:
|
||||
resolved "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
|
||||
integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
|
||||
|
||||
json5@2.x, json5@^2.1.0, json5@^2.1.1, json5@^2.1.2:
|
||||
json5@2.x, json5@^2.1.1, json5@^2.1.2:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
|
||||
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
|
||||
@@ -17456,7 +17407,7 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
|
||||
resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
|
||||
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
|
||||
|
||||
mimic-response@^2.0.0, mimic-response@^2.1.0:
|
||||
mimic-response@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
|
||||
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
|
||||
@@ -17526,6 +17477,13 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.
|
||||
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
minimisted@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz#d059fb905beecf0774bc3b308468699709805cb1"
|
||||
integrity sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
minipass-collect@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
|
||||
@@ -17969,23 +17927,6 @@ node-gyp@3.x:
|
||||
tar "^2.0.0"
|
||||
which "1"
|
||||
|
||||
node-gyp@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-4.0.0.tgz#972654af4e5dd0cd2a19081b4b46fe0442ba6f45"
|
||||
integrity sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA==
|
||||
dependencies:
|
||||
glob "^7.0.3"
|
||||
graceful-fs "^4.1.2"
|
||||
mkdirp "^0.5.0"
|
||||
nopt "2 || 3"
|
||||
npmlog "0 || 1 || 2 || 3 || 4"
|
||||
osenv "0"
|
||||
request "^2.87.0"
|
||||
rimraf "2"
|
||||
semver "~5.3.0"
|
||||
tar "^4.4.8"
|
||||
which "1"
|
||||
|
||||
node-gyp@^5.0.2:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz#8e31260a7af4a2e2f994b0673d4e0b3866156332"
|
||||
@@ -18075,22 +18016,6 @@ node-pre-gyp@^0.11.0:
|
||||
semver "^5.3.0"
|
||||
tar "^4"
|
||||
|
||||
node-pre-gyp@^0.13.0:
|
||||
version "0.13.0"
|
||||
resolved "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42"
|
||||
integrity sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==
|
||||
dependencies:
|
||||
detect-libc "^1.0.2"
|
||||
mkdirp "^0.5.1"
|
||||
needle "^2.2.1"
|
||||
nopt "^4.0.1"
|
||||
npm-packlist "^1.1.6"
|
||||
npmlog "^4.0.2"
|
||||
rc "^1.2.7"
|
||||
rimraf "^2.6.1"
|
||||
semver "^5.3.0"
|
||||
tar "^4"
|
||||
|
||||
node-releases@^1.1.52, node-releases@^1.1.58:
|
||||
version "1.1.60"
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084"
|
||||
@@ -18119,21 +18044,6 @@ node-request-interceptor@^0.5.1:
|
||||
debug "^4.1.1"
|
||||
headers-utils "^1.2.0"
|
||||
|
||||
nodegit@0.27.0, nodegit@^0.27.0:
|
||||
version "0.27.0"
|
||||
resolved "https://registry.npmjs.org/nodegit/-/nodegit-0.27.0.tgz#4e8cc236f60e1c97324a5acff99056fe116a6ebe"
|
||||
integrity sha512-E9K4gPjWiA0b3Tx5lfWCzG7Cvodi2idl3V5UD2fZrOrHikIfrN7Fc2kWLtMUqqomyoToYJLeIC8IV7xb1CYRLA==
|
||||
dependencies:
|
||||
fs-extra "^7.0.0"
|
||||
got "^10.7.0"
|
||||
json5 "^2.1.0"
|
||||
lodash "^4.17.14"
|
||||
nan "^2.14.0"
|
||||
node-gyp "^4.0.0"
|
||||
node-pre-gyp "^0.13.0"
|
||||
ramda "^0.25.0"
|
||||
tar-fs "^1.16.3"
|
||||
|
||||
nodemon@^2.0.2:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.6.tgz#1abe1937b463aaf62f0d52e2b7eaadf28cc2240d"
|
||||
@@ -18718,7 +18628,7 @@ p-each-series@^2.1.0:
|
||||
resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48"
|
||||
integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==
|
||||
|
||||
p-event@^4.0.0, p-event@^4.1.0:
|
||||
p-event@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5"
|
||||
integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==
|
||||
@@ -18908,7 +18818,7 @@ packet-reader@1.0.0:
|
||||
resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74"
|
||||
integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==
|
||||
|
||||
pako@~1.0.5:
|
||||
pako@^1.0.10, pako@~1.0.5:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
@@ -20253,14 +20163,6 @@ public-encrypt@^4.0.0:
|
||||
randombytes "^2.0.1"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
pump@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954"
|
||||
integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==
|
||||
dependencies:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
pump@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
|
||||
@@ -20406,11 +20308,6 @@ ramda@^0.21.0:
|
||||
resolved "https://registry.npmjs.org/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35"
|
||||
integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU=
|
||||
|
||||
ramda@^0.25.0:
|
||||
version "0.25.0"
|
||||
resolved "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9"
|
||||
integrity sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==
|
||||
|
||||
ramda@^0.26, ramda@~0.26.1:
|
||||
version "0.26.1"
|
||||
resolved "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
|
||||
@@ -21169,7 +21066,7 @@ read@1, read@~1.0.1:
|
||||
dependencies:
|
||||
mute-stream "~0.0.4"
|
||||
|
||||
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
|
||||
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
||||
@@ -22243,7 +22140,7 @@ setprototypeof@1.2.0:
|
||||
resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
|
||||
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
|
||||
|
||||
sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8:
|
||||
sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8, sha.js@^2.4.9:
|
||||
version "2.4.11"
|
||||
resolved "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
||||
integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
|
||||
@@ -22354,7 +22251,7 @@ simple-concat@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
|
||||
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
|
||||
|
||||
simple-get@^3.0.3:
|
||||
simple-get@^3.0.2, simple-get@^3.0.3:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
|
||||
integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
|
||||
@@ -23465,16 +23362,6 @@ tapable@^1.0.0, tapable@^1.1.3:
|
||||
resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
||||
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
||||
|
||||
tar-fs@^1.16.3:
|
||||
version "1.16.3"
|
||||
resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509"
|
||||
integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==
|
||||
dependencies:
|
||||
chownr "^1.0.1"
|
||||
mkdirp "^0.5.1"
|
||||
pump "^1.0.0"
|
||||
tar-stream "^1.1.2"
|
||||
|
||||
tar-fs@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2"
|
||||
@@ -23485,19 +23372,6 @@ tar-fs@~2.0.1:
|
||||
pump "^3.0.0"
|
||||
tar-stream "^2.0.0"
|
||||
|
||||
tar-stream@^1.1.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
|
||||
dependencies:
|
||||
bl "^1.0.0"
|
||||
buffer-alloc "^1.2.0"
|
||||
end-of-stream "^1.0.0"
|
||||
fs-constants "^1.0.0"
|
||||
readable-stream "^2.3.0"
|
||||
to-buffer "^1.1.1"
|
||||
xtend "^4.0.0"
|
||||
|
||||
tar-stream@^2.0.0, tar-stream@^2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz#c4fb1a11eb0da29b893a5b25476397ba2d053bfa"
|
||||
@@ -23837,11 +23711,6 @@ to-arraybuffer@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
|
||||
integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
|
||||
|
||||
to-buffer@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
|
||||
|
||||
to-fast-properties@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
||||
@@ -23859,11 +23728,6 @@ to-readable-stream@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
|
||||
integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
|
||||
|
||||
to-readable-stream@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz#82880316121bea662cdc226adb30addb50cb06e8"
|
||||
integrity sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==
|
||||
|
||||
to-regex-range@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"
|
||||
@@ -24164,11 +24028,6 @@ type-detect@4.0.8:
|
||||
resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
|
||||
type-fest@^0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz#7f06b2b9fbfc581068d1341ffabd0349ceafc642"
|
||||
integrity sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==
|
||||
|
||||
type-fest@^0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
|
||||
|
||||
Reference in New Issue
Block a user