Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ PRIVATE_ORG=

# Used to skip branch protection creation if organization level branch protections are used instead
SKIP_BRANCH_PROTECTION_CREATION=

# Used to create mirrors with internal visibility instead of private for use in GitHub Enterprise Cloud organizations
CREATE_MIRRORS_WITH_INTERNAL_VISIBILITY=
7 changes: 7 additions & 0 deletions env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ export const env = createEnv({
.optional()
.default('false')
.transform((value) => value === 'true'),
CREATE_MIRRORS_WITH_INTERNAL_VISIBILITY: z
.enum(['true', 'false', ''])
.optional()
.default('false')
.transform((value) => value === 'true'),
},
/*
* Environment variables available on the client (and server).
Expand Down Expand Up @@ -73,6 +78,8 @@ export const env = createEnv({
ALLOWED_ORGS: process.env.ALLOWED_ORGS,
SKIP_BRANCH_PROTECTION_CREATION:
process.env.SKIP_BRANCH_PROTECTION_CREATION,
CREATE_MIRRORS_WITH_INTERNAL_VISIBILITY:
process.env.CREATE_MIRRORS_WITH_INTERNAL_VISIBILITY,
},
skipValidation: process.env.SKIP_ENV_VALIDATIONS === 'true',
})
5 changes: 4 additions & 1 deletion src/server/repos/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ export const createMirrorHandler = async ({
newRepo = await privateOctokit.rest.repos.createInOrg({
name: input.newRepoName,
org: privateOrg,
private: true,
// @ts-expect-error because the rest API accepts internal as an option but the types aren't up to date
visibility: process.env.CREATE_MIRRORS_WITH_INTERNAL_VISIBILITY
? 'internal'
: 'private',
description: `Mirror of ${input.forkRepoOwner}/${input.forkRepoName}`,
custom_properties: {
fork: `${input.forkRepoOwner}/${input.forkRepoName}`,
Expand Down
58 changes: 57 additions & 1 deletion test/server/repos.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Octomock } from '../octomock'
import { createTestContext } from '../utils/auth'
import { t } from '../../src/utils/trpc-server'
const om = new Octomock()
const UNMODIFIED_ENV = process.env

jest.mock('../../src/bot/config')
jest.mock('../../src/bot/octokit', () => ({
Expand Down Expand Up @@ -104,9 +105,10 @@ describe('Repos router', () => {
beforeEach(() => {
om.resetMocks()
jest.resetAllMocks()
process.env = { ...UNMODIFIED_ENV }
})

it('should create a mirror when repo does not exist exist', async () => {
it('should create a mirror when repo does not exist', async () => {
const caller = t.createCallerFactory(reposRouter)(createTestContext())

const configSpy = jest.spyOn(config, 'getConfig').mockResolvedValue({
Expand Down Expand Up @@ -284,6 +286,60 @@ describe('Repos router', () => {
expect(stubbedGit.clone).toHaveBeenCalledTimes(1)
})

it('should create an internal repo when the CREATE_MIRRORS_WITH_INTERNAL_VISIBILITY flag is used', async () => {
const caller = t.createCallerFactory(reposRouter)(createTestContext())

const configSpy = jest.spyOn(config, 'getConfig').mockResolvedValue({
publicOrg: 'github',
privateOrg: 'github-test',
})

om.mockFunctions.rest.apps.getOrgInstallation.mockResolvedValue(
fakeOrgInstallation,
)
om.mockFunctions.rest.orgs.get.mockResolvedValue(fakeOrg)
om.mockFunctions.rest.repos.get.mockResolvedValueOnce(repoNotFound)
om.mockFunctions.rest.repos.get.mockResolvedValueOnce(fakeForkRepo)
om.mockFunctions.rest.orgs.getAllCustomProperties.mockResolvedValue(
fakeOrgCustomProperties,
)
om.mockFunctions.rest.orgs.createOrUpdateCustomProperty.mockResolvedValue(
fakeOrgCustomProperties,
)
om.mockFunctions.rest.repos.createInOrg.mockResolvedValue(fakeMirrorRepo)

// set the environment variable to trigger mirrors being created with internal visibility
process.env.CREATE_MIRRORS_WITH_INTERNAL_VISIBILITY = 'true'

const res = await caller.createMirror({
forkId: 'test',
orgId: 'test',
forkRepoName: 'fork-test',
forkRepoOwner: 'github',
newBranchName: 'test',
newRepoName: 'test',
})

// TODO: use real git operations and verify fs state after
expect(configSpy).toHaveBeenCalledTimes(1)
expect(om.mockFunctions.rest.repos.get).toHaveBeenCalledTimes(2)
expect(stubbedGit.clone).toHaveBeenCalledTimes(1)
expect(om.mockFunctions.rest.repos.createInOrg).toHaveBeenCalledTimes(1)
expect(om.mockFunctions.rest.repos.createInOrg).toHaveBeenCalledWith(
expect.objectContaining({
visibility: 'internal',
}),
)
expect(stubbedGit.addRemote).toHaveBeenCalledTimes(1)
expect(stubbedGit.push).toHaveBeenCalledTimes(2)
expect(stubbedGit.checkoutBranch).toHaveBeenCalledTimes(1)

expect(res).toEqual({
success: true,
data: fakeMirrorRepo.data,
})
})

it('reject repository names over the character limit', async () => {
const caller = t.createCallerFactory(reposRouter)(createTestContext())

Expand Down
Loading