Merge pull request #2429 from taras/tm/scaffold-repo-visibility
Add the ability to configure repository visibility via config
This commit is contained in:
@@ -53,14 +53,186 @@ const {
|
||||
};
|
||||
|
||||
describe('GitHub Publisher', () => {
|
||||
const publisher = new GithubPublisher({ client: new Octokit() });
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('publish: createRemoteInGithub', () => {
|
||||
it('should use octokit to create a repo in an organisation if the organisation property is set', async () => {
|
||||
describe('with public repo visibility', () => {
|
||||
const publisher = new GithubPublisher({
|
||||
client: new Octokit(),
|
||||
token: 'abc',
|
||||
repoVisibility: 'public',
|
||||
});
|
||||
|
||||
describe('publish: createRemoteInGithub', () => {
|
||||
it('should use octokit to create a repo in an organisation if the organisation property is set', async () => {
|
||||
mockGithubClient.repos.createInOrg.mockResolvedValue({
|
||||
data: {
|
||||
clone_url: 'mockclone',
|
||||
},
|
||||
} as OctokitResponse<ReposCreateInOrgResponseData>);
|
||||
|
||||
await publisher.publish({
|
||||
values: {
|
||||
storePath: 'blam/test',
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
});
|
||||
|
||||
expect(mockGithubClient.repos.createInOrg).toHaveBeenCalledWith({
|
||||
org: 'blam',
|
||||
name: 'test',
|
||||
private: false,
|
||||
visibility: 'public',
|
||||
});
|
||||
});
|
||||
|
||||
it('should use octokit to create a repo in the authed user if the organisation property is not set', async () => {
|
||||
mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({
|
||||
data: {
|
||||
clone_url: 'mockclone',
|
||||
},
|
||||
} as OctokitResponse<ReposCreateInOrgResponseData>);
|
||||
mockGithubClient.users.getByUsername.mockResolvedValue({
|
||||
data: {
|
||||
type: 'User',
|
||||
},
|
||||
} as OctokitResponse<UsersGetByUsernameResponseData>);
|
||||
|
||||
await publisher.publish({
|
||||
values: {
|
||||
storePath: 'blam/test',
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
});
|
||||
|
||||
expect(
|
||||
mockGithubClient.repos.createForAuthenticatedUser,
|
||||
).toHaveBeenCalledWith({
|
||||
name: 'test',
|
||||
private: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('publish: createGitDirectory', () => {
|
||||
const values = {
|
||||
isOrg: true,
|
||||
storePath: 'blam/test',
|
||||
owner: 'lols',
|
||||
};
|
||||
|
||||
const mockDir = '/tmp/test/dir';
|
||||
|
||||
mockGithubClient.repos.createInOrg.mockResolvedValue({
|
||||
data: {
|
||||
clone_url: 'mockclone',
|
||||
},
|
||||
} as OctokitResponse<ReposCreateInOrgResponseData>);
|
||||
mockGithubClient.users.getByUsername.mockResolvedValue({
|
||||
data: {
|
||||
type: 'Organization',
|
||||
},
|
||||
} as OctokitResponse<UsersGetByUsernameResponseData>);
|
||||
|
||||
it('should call init on the repo with the directory', async () => {
|
||||
await publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
expect(Repository.init).toHaveBeenCalledWith(mockDir, 0);
|
||||
});
|
||||
|
||||
it('should call refresh index on the index and write the new files', async () => {
|
||||
await publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
expect(mockRepo.refreshIndex).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call add all files and write', async () => {
|
||||
await publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
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 publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
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 publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
expect(Remote.create).toHaveBeenCalledWith(
|
||||
mockRepo,
|
||||
'origin',
|
||||
'mockclone',
|
||||
);
|
||||
});
|
||||
|
||||
it('shoud push to the remote repo', async () => {
|
||||
await publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
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(
|
||||
'abc',
|
||||
'x-oauth-basic',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with internal repo visibility', () => {
|
||||
const publisher = new GithubPublisher({
|
||||
client: new Octokit(),
|
||||
token: 'abc',
|
||||
repoVisibility: 'internal',
|
||||
});
|
||||
|
||||
it('creates a private repository in the organization with visibility set to internal', async () => {
|
||||
mockGithubClient.repos.createInOrg.mockResolvedValue({
|
||||
data: {
|
||||
clone_url: 'mockclone',
|
||||
@@ -74,6 +246,7 @@ describe('GitHub Publisher', () => {
|
||||
|
||||
await publisher.publish({
|
||||
values: {
|
||||
isOrg: true,
|
||||
storePath: 'blam/test',
|
||||
owner: 'bob',
|
||||
},
|
||||
@@ -83,10 +256,20 @@ describe('GitHub Publisher', () => {
|
||||
expect(mockGithubClient.repos.createInOrg).toHaveBeenCalledWith({
|
||||
org: 'blam',
|
||||
name: 'test',
|
||||
private: true,
|
||||
visibility: 'internal',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should use octokit to create a repo in the authed user if the organisation property is not set', async () => {
|
||||
describe('private visibility in a user account', () => {
|
||||
const publisher = new GithubPublisher({
|
||||
client: new Octokit(),
|
||||
token: 'abc',
|
||||
repoVisibility: 'private',
|
||||
});
|
||||
|
||||
it('creates a private repository', async () => {
|
||||
mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({
|
||||
data: {
|
||||
clone_url: 'mockclone',
|
||||
@@ -110,116 +293,8 @@ describe('GitHub Publisher', () => {
|
||||
mockGithubClient.repos.createForAuthenticatedUser,
|
||||
).toHaveBeenCalledWith({
|
||||
name: 'test',
|
||||
private: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('publish: createGitDirectory', () => {
|
||||
const values = {
|
||||
isOrg: true,
|
||||
storePath: 'blam/test',
|
||||
owner: 'lols',
|
||||
};
|
||||
|
||||
const mockDir = '/tmp/test/dir';
|
||||
|
||||
mockGithubClient.repos.createInOrg.mockResolvedValue({
|
||||
data: {
|
||||
clone_url: 'mockclone',
|
||||
},
|
||||
} as OctokitResponse<ReposCreateInOrgResponseData>);
|
||||
mockGithubClient.users.getByUsername.mockResolvedValue({
|
||||
data: {
|
||||
type: 'Organization',
|
||||
},
|
||||
} as OctokitResponse<UsersGetByUsernameResponseData>);
|
||||
|
||||
it('should call init on the repo with the directory', async () => {
|
||||
await publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
expect(Repository.init).toHaveBeenCalledWith(mockDir, 0);
|
||||
});
|
||||
|
||||
it('should call refresh index on the index and write the new files', async () => {
|
||||
await publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
expect(mockRepo.refreshIndex).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call add all files and write', async () => {
|
||||
await publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
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 publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
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 publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
expect(Remote.create).toHaveBeenCalledWith(
|
||||
mockRepo,
|
||||
'origin',
|
||||
'mockclone',
|
||||
);
|
||||
});
|
||||
|
||||
it('shoud push to the remote repo', async () => {
|
||||
await publisher.publish({
|
||||
values,
|
||||
directory: mockDir,
|
||||
});
|
||||
|
||||
const [remotes, { callbacks }] = mockRemote.push.mock
|
||||
.calls[0] as NodeGit.PushOptions[];
|
||||
|
||||
expect(remotes).toEqual(['refs/heads/master:refs/heads/master']);
|
||||
|
||||
process.env.GITHUb_ACCESS_TOKEN = 'blob';
|
||||
|
||||
callbacks?.credentials?.();
|
||||
|
||||
expect(Cred.userpassPlaintextNew).toHaveBeenCalledWith(
|
||||
process.env.GITHUB_ACCESS_TOKEN,
|
||||
'x-oauth-basic',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,10 +21,27 @@ import { JsonValue } from '@backstage/config';
|
||||
import { RequiredTemplateValues } from '../templater';
|
||||
import { Repository, Remote, Signature, Cred } from 'nodegit';
|
||||
|
||||
export type RepoVisilityOptions = 'private' | 'internal' | 'public';
|
||||
|
||||
interface GithubPublisherParams {
|
||||
client: Octokit;
|
||||
token: string;
|
||||
repoVisibility: RepoVisilityOptions;
|
||||
}
|
||||
|
||||
export class GithubPublisher implements PublisherBase {
|
||||
private client: Octokit;
|
||||
constructor({ client }: { client: Octokit }) {
|
||||
private token: string;
|
||||
private repoVisibility: RepoVisilityOptions;
|
||||
|
||||
constructor({
|
||||
client,
|
||||
token,
|
||||
repoVisibility = 'public',
|
||||
}: GithubPublisherParams) {
|
||||
this.client = client;
|
||||
this.token = token;
|
||||
this.repoVisibility = repoVisibility;
|
||||
}
|
||||
|
||||
async publish({
|
||||
@@ -49,8 +66,16 @@ export class GithubPublisher implements PublisherBase {
|
||||
|
||||
const repoCreationPromise =
|
||||
user.data.type === 'Organization'
|
||||
? this.client.repos.createInOrg({ name, org: owner })
|
||||
: this.client.repos.createForAuthenticatedUser({ name });
|
||||
? this.client.repos.createInOrg({
|
||||
name,
|
||||
org: owner,
|
||||
private: this.repoVisibility !== 'public',
|
||||
visibility: this.repoVisibility,
|
||||
})
|
||||
: this.client.repos.createForAuthenticatedUser({
|
||||
name,
|
||||
private: this.repoVisibility === 'private',
|
||||
});
|
||||
|
||||
const { data } = await repoCreationPromise;
|
||||
|
||||
@@ -76,10 +101,7 @@ export class GithubPublisher implements PublisherBase {
|
||||
await remoteRepo.push(['refs/heads/master:refs/heads/master'], {
|
||||
callbacks: {
|
||||
credentials: () => {
|
||||
return Cred.userpassPlaintextNew(
|
||||
process.env.GITHUB_ACCESS_TOKEN as string,
|
||||
'x-oauth-basic',
|
||||
);
|
||||
return Cred.userpassPlaintextNew(this.token, 'x-oauth-basic');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user