refactor repo create, repo push
Signed-off-by: Marco Crivellaro <marco.crive@gmail.com>
This commit is contained in:
+29
-120
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { assertError, InputError } from '@backstage/errors';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import {
|
||||
GithubCredentialsProvider,
|
||||
ScmIntegrationRegistry,
|
||||
@@ -21,7 +21,10 @@ import {
|
||||
import { Octokit } from 'octokit';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import { parseRepoUrl } from '../publish/util';
|
||||
import { getOctokitOptions } from './helpers';
|
||||
import {
|
||||
createGithubRepoWithCollaboratorsAndTopics,
|
||||
getOctokitOptions,
|
||||
} from './helpers';
|
||||
import * as inputProps from './inputProperties';
|
||||
import * as outputProps from './outputProperties';
|
||||
|
||||
@@ -58,6 +61,11 @@ export function createGithubRepoCreateAction(options: {
|
||||
team: string;
|
||||
access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage';
|
||||
}
|
||||
| {
|
||||
/** @deprecated This field is deprecated in favor of team */
|
||||
username: string;
|
||||
access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage';
|
||||
}
|
||||
>;
|
||||
token?: string;
|
||||
topics?: string[];
|
||||
@@ -107,136 +115,37 @@ export function createGithubRepoCreateAction(options: {
|
||||
token: providedToken,
|
||||
} = ctx.input;
|
||||
|
||||
const { owner, repo } = parseRepoUrl(repoUrl, integrations);
|
||||
|
||||
if (!owner) {
|
||||
throw new InputError('Invalid repository owner provided in repoUrl');
|
||||
}
|
||||
|
||||
const octokitOptions = await getOctokitOptions({
|
||||
integrations,
|
||||
credentialsProvider: githubCredentialsProvider,
|
||||
token: providedToken,
|
||||
repoUrl: repoUrl,
|
||||
});
|
||||
|
||||
const client = new Octokit(octokitOptions);
|
||||
|
||||
const user = await client.rest.users.getByUsername({
|
||||
username: owner,
|
||||
});
|
||||
const { owner, repo } = parseRepoUrl(repoUrl, integrations);
|
||||
|
||||
const repoCreationPromise =
|
||||
user.data.type === 'Organization'
|
||||
? client.rest.repos.createInOrg({
|
||||
name: repo,
|
||||
org: owner,
|
||||
private: repoVisibility === 'private',
|
||||
visibility: repoVisibility,
|
||||
description: description,
|
||||
delete_branch_on_merge: deleteBranchOnMerge,
|
||||
allow_merge_commit: allowMergeCommit,
|
||||
allow_squash_merge: allowSquashMerge,
|
||||
allow_rebase_merge: allowRebaseMerge,
|
||||
})
|
||||
: client.rest.repos.createForAuthenticatedUser({
|
||||
name: repo,
|
||||
private: repoVisibility === 'private',
|
||||
description: description,
|
||||
delete_branch_on_merge: deleteBranchOnMerge,
|
||||
allow_merge_commit: allowMergeCommit,
|
||||
allow_squash_merge: allowSquashMerge,
|
||||
allow_rebase_merge: allowRebaseMerge,
|
||||
});
|
||||
|
||||
let newRepo;
|
||||
|
||||
try {
|
||||
newRepo = (await repoCreationPromise).data;
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
if (e.message === 'Resource not accessible by integration') {
|
||||
ctx.logger.warn(
|
||||
`The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`,
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
`Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`,
|
||||
);
|
||||
if (!owner) {
|
||||
throw new InputError('Invalid repository owner provided in repoUrl');
|
||||
}
|
||||
|
||||
if (access?.startsWith(`${owner}/`)) {
|
||||
const [, team] = access.split('/');
|
||||
await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
|
||||
org: owner,
|
||||
team_slug: team,
|
||||
owner,
|
||||
repo,
|
||||
permission: 'admin',
|
||||
});
|
||||
// No need to add access if it's the person who owns the personal account
|
||||
} else if (access && access !== owner) {
|
||||
await client.rest.repos.addCollaborator({
|
||||
owner,
|
||||
repo,
|
||||
username: access,
|
||||
permission: 'admin',
|
||||
});
|
||||
}
|
||||
const newRepo = await createGithubRepoWithCollaboratorsAndTopics(
|
||||
client,
|
||||
repo,
|
||||
owner,
|
||||
repoVisibility,
|
||||
description,
|
||||
deleteBranchOnMerge,
|
||||
allowMergeCommit,
|
||||
allowSquashMerge,
|
||||
allowRebaseMerge,
|
||||
access,
|
||||
collaborators,
|
||||
topics,
|
||||
ctx.logger,
|
||||
);
|
||||
|
||||
if (collaborators) {
|
||||
for (const collaborator of collaborators) {
|
||||
try {
|
||||
if ('user' in collaborator) {
|
||||
await client.rest.repos.addCollaborator({
|
||||
owner,
|
||||
repo,
|
||||
username: collaborator.user,
|
||||
permission: collaborator.access,
|
||||
});
|
||||
} else if ('team' in collaborator) {
|
||||
await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
|
||||
org: owner,
|
||||
team_slug: collaborator.team,
|
||||
owner,
|
||||
repo,
|
||||
permission: collaborator.access,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
const name = extractCollaboratorName(collaborator);
|
||||
ctx.logger.warn(
|
||||
`Skipping ${collaborator.access} access for ${name}, ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (topics) {
|
||||
try {
|
||||
await client.rest.repos.replaceAllTopics({
|
||||
owner,
|
||||
repo,
|
||||
names: topics.map(t => t.toLowerCase()),
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
ctx.logger.warn(`Skipping topics ${topics.join(' ')}, ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const remoteUrl = newRepo.clone_url;
|
||||
|
||||
ctx.output('remoteUrl', remoteUrl);
|
||||
ctx.output('remoteUrl', newRepo.clone_url);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function extractCollaboratorName(
|
||||
collaborator: { user: string } | { team: string } | { username: string },
|
||||
) {
|
||||
if ('username' in collaborator) return collaborator.username;
|
||||
if ('user' in collaborator) return collaborator.user;
|
||||
return collaborator.team;
|
||||
}
|
||||
|
||||
+19
-47
@@ -14,19 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Config } from '@backstage/config';
|
||||
import { assertError, InputError } from '@backstage/errors';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import {
|
||||
GithubCredentialsProvider,
|
||||
ScmIntegrationRegistry,
|
||||
} from '@backstage/integration';
|
||||
import { Octokit } from 'octokit';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import {
|
||||
enableBranchProtectionOnDefaultRepoBranch,
|
||||
initRepoAndPush,
|
||||
} from '../helpers';
|
||||
import { getRepoSourceDirectory, parseRepoUrl } from '../publish/util';
|
||||
import { getOctokitOptions } from './helpers';
|
||||
import { parseRepoUrl } from '../publish/util';
|
||||
import { getOctokitOptions, initRepoPushAndProtect } from './helpers';
|
||||
import * as inputProps from './inputProperties';
|
||||
import * as outputProps from './outputProperties';
|
||||
|
||||
@@ -117,48 +113,24 @@ export function createGithubRepoPushAction(options: {
|
||||
const remoteUrl = targetRepo.data.clone_url;
|
||||
const repoContentsUrl = `${targetRepo.data.html_url}/blob/${defaultBranch}`;
|
||||
|
||||
const gitAuthorInfo = {
|
||||
name: gitAuthorName
|
||||
? gitAuthorName
|
||||
: config.getOptionalString('scaffolder.defaultAuthor.name'),
|
||||
email: gitAuthorEmail
|
||||
? gitAuthorEmail
|
||||
: config.getOptionalString('scaffolder.defaultAuthor.email'),
|
||||
};
|
||||
|
||||
await initRepoAndPush({
|
||||
dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
|
||||
await initRepoPushAndProtect(
|
||||
remoteUrl,
|
||||
octokitOptions.auth,
|
||||
ctx.workspacePath,
|
||||
ctx.input.sourcePath,
|
||||
defaultBranch,
|
||||
auth: {
|
||||
username: 'x-access-token',
|
||||
password: octokitOptions.auth,
|
||||
},
|
||||
logger: ctx.logger,
|
||||
commitMessage: gitCommitMessage
|
||||
? gitCommitMessage
|
||||
: config.getOptionalString('scaffolder.defaultCommitMessage'),
|
||||
gitAuthorInfo,
|
||||
});
|
||||
|
||||
if (protectDefaultBranch) {
|
||||
try {
|
||||
await enableBranchProtectionOnDefaultRepoBranch({
|
||||
owner,
|
||||
client,
|
||||
repoName: repo,
|
||||
logger: ctx.logger,
|
||||
defaultBranch,
|
||||
requireCodeOwnerReviews,
|
||||
requiredStatusCheckContexts,
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
ctx.logger.warn(
|
||||
`Skipping: default branch protection on '${repo}', ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
protectDefaultBranch,
|
||||
owner,
|
||||
client,
|
||||
repo,
|
||||
requireCodeOwnerReviews,
|
||||
requiredStatusCheckContexts,
|
||||
config,
|
||||
ctx.logger,
|
||||
gitCommitMessage,
|
||||
gitAuthorName,
|
||||
gitAuthorEmail,
|
||||
);
|
||||
|
||||
ctx.output('remoteUrl', remoteUrl);
|
||||
ctx.output('repoContentsUrl', repoContentsUrl);
|
||||
|
||||
@@ -13,14 +13,21 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { Config } from '@backstage/config';
|
||||
import { assertError, InputError } from '@backstage/errors';
|
||||
import {
|
||||
DefaultGithubCredentialsProvider,
|
||||
GithubCredentialsProvider,
|
||||
ScmIntegrationRegistry,
|
||||
} from '@backstage/integration';
|
||||
import { OctokitOptions } from '@octokit/core/dist-types/types';
|
||||
import { parseRepoUrl } from '../publish/util';
|
||||
import { Octokit } from 'octokit';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
enableBranchProtectionOnDefaultRepoBranch,
|
||||
initRepoAndPush,
|
||||
} from '../helpers';
|
||||
import { getRepoSourceDirectory, parseRepoUrl } from '../publish/util';
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 60_000;
|
||||
|
||||
@@ -83,3 +90,213 @@ export async function getOctokitOptions(options: {
|
||||
previews: ['nebula-preview'],
|
||||
};
|
||||
}
|
||||
|
||||
export async function createGithubRepoWithCollaboratorsAndTopics(
|
||||
client: Octokit,
|
||||
repo: string,
|
||||
owner: string,
|
||||
repoVisibility: 'private' | 'internal' | 'public',
|
||||
description: string | undefined,
|
||||
deleteBranchOnMerge: boolean,
|
||||
allowMergeCommit: boolean,
|
||||
allowSquashMerge: boolean,
|
||||
allowRebaseMerge: boolean,
|
||||
access: string | undefined,
|
||||
collaborators:
|
||||
| (
|
||||
| {
|
||||
user: string;
|
||||
access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage';
|
||||
}
|
||||
| {
|
||||
team: string;
|
||||
access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage';
|
||||
}
|
||||
| {
|
||||
/** @deprecated This field is deprecated in favor of team */
|
||||
username: string;
|
||||
access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage';
|
||||
}
|
||||
)[]
|
||||
| undefined,
|
||||
topics: string[] | undefined,
|
||||
logger: Logger,
|
||||
) {
|
||||
const user = await client.rest.users.getByUsername({
|
||||
username: owner,
|
||||
});
|
||||
|
||||
const repoCreationPromise =
|
||||
user.data.type === 'Organization'
|
||||
? client.rest.repos.createInOrg({
|
||||
name: repo,
|
||||
org: owner,
|
||||
private: repoVisibility === 'private',
|
||||
visibility: repoVisibility,
|
||||
description: description,
|
||||
delete_branch_on_merge: deleteBranchOnMerge,
|
||||
allow_merge_commit: allowMergeCommit,
|
||||
allow_squash_merge: allowSquashMerge,
|
||||
allow_rebase_merge: allowRebaseMerge,
|
||||
})
|
||||
: client.rest.repos.createForAuthenticatedUser({
|
||||
name: repo,
|
||||
private: repoVisibility === 'private',
|
||||
description: description,
|
||||
delete_branch_on_merge: deleteBranchOnMerge,
|
||||
allow_merge_commit: allowMergeCommit,
|
||||
allow_squash_merge: allowSquashMerge,
|
||||
allow_rebase_merge: allowRebaseMerge,
|
||||
});
|
||||
|
||||
let newRepo;
|
||||
|
||||
try {
|
||||
newRepo = (await repoCreationPromise).data;
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
if (e.message === 'Resource not accessible by integration') {
|
||||
logger.warn(
|
||||
`The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`,
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
`Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (access?.startsWith(`${owner}/`)) {
|
||||
const [, team] = access.split('/');
|
||||
await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
|
||||
org: owner,
|
||||
team_slug: team,
|
||||
owner,
|
||||
repo,
|
||||
permission: 'admin',
|
||||
});
|
||||
// No need to add access if it's the person who owns the personal account
|
||||
} else if (access && access !== owner) {
|
||||
await client.rest.repos.addCollaborator({
|
||||
owner,
|
||||
repo,
|
||||
username: access,
|
||||
permission: 'admin',
|
||||
});
|
||||
}
|
||||
|
||||
if (collaborators) {
|
||||
for (const collaborator of collaborators) {
|
||||
try {
|
||||
if ('user' in collaborator) {
|
||||
await client.rest.repos.addCollaborator({
|
||||
owner,
|
||||
repo,
|
||||
username: collaborator.user,
|
||||
permission: collaborator.access,
|
||||
});
|
||||
} else if ('team' in collaborator) {
|
||||
await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
|
||||
org: owner,
|
||||
team_slug: collaborator.team,
|
||||
owner,
|
||||
repo,
|
||||
permission: collaborator.access,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
const name = extractCollaboratorName(collaborator);
|
||||
logger.warn(
|
||||
`Skipping ${collaborator.access} access for ${name}, ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (topics) {
|
||||
try {
|
||||
await client.rest.repos.replaceAllTopics({
|
||||
owner,
|
||||
repo,
|
||||
names: topics.map(t => t.toLowerCase()),
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
logger.warn(`Skipping topics ${topics.join(' ')}, ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return newRepo;
|
||||
}
|
||||
|
||||
export async function initRepoPushAndProtect(
|
||||
remoteUrl: string,
|
||||
password: string,
|
||||
workspacePath: string,
|
||||
sourcePath: string | undefined,
|
||||
defaultBranch: string,
|
||||
protectDefaultBranch: boolean,
|
||||
owner: string,
|
||||
client: Octokit,
|
||||
repo: string,
|
||||
requireCodeOwnerReviews: boolean,
|
||||
requiredStatusCheckContexts: string[],
|
||||
config: Config,
|
||||
logger: any,
|
||||
gitCommitMessage?: string,
|
||||
gitAuthorName?: string,
|
||||
gitAuthorEmail?: string,
|
||||
) {
|
||||
const gitAuthorInfo = {
|
||||
name: gitAuthorName
|
||||
? gitAuthorName
|
||||
: config.getOptionalString('scaffolder.defaultAuthor.name'),
|
||||
email: gitAuthorEmail
|
||||
? gitAuthorEmail
|
||||
: config.getOptionalString('scaffolder.defaultAuthor.email'),
|
||||
};
|
||||
|
||||
const commitMessage = gitCommitMessage
|
||||
? gitCommitMessage
|
||||
: config.getOptionalString('scaffolder.defaultCommitMessage');
|
||||
|
||||
await initRepoAndPush({
|
||||
dir: getRepoSourceDirectory(workspacePath, sourcePath),
|
||||
remoteUrl,
|
||||
defaultBranch,
|
||||
auth: {
|
||||
username: 'x-access-token',
|
||||
password,
|
||||
},
|
||||
logger,
|
||||
commitMessage,
|
||||
gitAuthorInfo,
|
||||
});
|
||||
|
||||
if (protectDefaultBranch) {
|
||||
try {
|
||||
await enableBranchProtectionOnDefaultRepoBranch({
|
||||
owner,
|
||||
client,
|
||||
repoName: repo,
|
||||
logger,
|
||||
defaultBranch,
|
||||
requireCodeOwnerReviews,
|
||||
requiredStatusCheckContexts,
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
logger.warn(
|
||||
`Skipping: default branch protection on '${repo}', ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function extractCollaboratorName(
|
||||
collaborator: { user: string } | { team: string } | { username: string },
|
||||
) {
|
||||
if ('username' in collaborator) return collaborator.username;
|
||||
if ('user' in collaborator) return collaborator.user;
|
||||
return collaborator.team;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SpawnOptionsWithoutStdio, spawn } from 'child_process';
|
||||
import { Git } from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import { assertError } from '@backstage/errors';
|
||||
import { spawn, SpawnOptionsWithoutStdio } from 'child_process';
|
||||
import { Octokit } from 'octokit';
|
||||
import { PassThrough, Writable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
import { Git } from '@backstage/backend-common';
|
||||
import { Octokit } from 'octokit';
|
||||
import { assertError } from '@backstage/errors';
|
||||
|
||||
/** @public */
|
||||
export type RunCommandOptions = {
|
||||
@@ -200,3 +201,12 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({
|
||||
await tryOnce();
|
||||
}
|
||||
};
|
||||
|
||||
export function getGitCommitMessage(
|
||||
gitCommitMessage: string | undefined,
|
||||
config: Config,
|
||||
): string | undefined {
|
||||
return gitCommitMessage
|
||||
? gitCommitMessage
|
||||
: config.getOptionalString('scaffolder.defaultCommitMessage');
|
||||
}
|
||||
|
||||
@@ -14,22 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Config } from '@backstage/config';
|
||||
import { assertError, InputError } from '@backstage/errors';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import {
|
||||
GithubCredentialsProvider,
|
||||
ScmIntegrationRegistry,
|
||||
} from '@backstage/integration';
|
||||
import { Octokit } from 'octokit';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import { getOctokitOptions } from '../github/helpers';
|
||||
import {
|
||||
createGithubRepoWithCollaboratorsAndTopics,
|
||||
getOctokitOptions,
|
||||
initRepoPushAndProtect,
|
||||
} from '../github/helpers';
|
||||
import * as inputProps from '../github/inputProperties';
|
||||
import * as outputProps from '../github/outputProperties';
|
||||
import {
|
||||
enableBranchProtectionOnDefaultRepoBranch,
|
||||
initRepoAndPush,
|
||||
} from '../helpers';
|
||||
import { getRepoSourceDirectory, parseRepoUrl } from './util';
|
||||
|
||||
import { parseRepoUrl } from './util';
|
||||
/**
|
||||
* Creates a new action that initializes a git repository of the content in the workspace
|
||||
* and publishes it to GitHub.
|
||||
@@ -137,192 +136,103 @@ export function createPublishGithubAction(options: {
|
||||
token: providedToken,
|
||||
} = ctx.input;
|
||||
|
||||
const octokitOptions = await getOctokitOptions({
|
||||
integrations,
|
||||
credentialsProvider: githubCredentialsProvider,
|
||||
token: providedToken,
|
||||
repoUrl: repoUrl,
|
||||
});
|
||||
const client = new Octokit(octokitOptions);
|
||||
|
||||
const { owner, repo } = parseRepoUrl(repoUrl, integrations);
|
||||
|
||||
if (!owner) {
|
||||
throw new InputError('Invalid repository owner provided in repoUrl');
|
||||
}
|
||||
|
||||
const octokitOptions = await getOctokitOptions({
|
||||
integrations,
|
||||
credentialsProvider: githubCredentialsProvider,
|
||||
token: providedToken,
|
||||
repoUrl,
|
||||
});
|
||||
|
||||
const client = new Octokit(octokitOptions);
|
||||
|
||||
const user = await client.rest.users.getByUsername({
|
||||
username: owner,
|
||||
});
|
||||
|
||||
const repoCreationPromise =
|
||||
user.data.type === 'Organization'
|
||||
? client.rest.repos.createInOrg({
|
||||
name: repo,
|
||||
org: owner,
|
||||
private: repoVisibility === 'private',
|
||||
visibility: repoVisibility,
|
||||
description: description,
|
||||
delete_branch_on_merge: deleteBranchOnMerge,
|
||||
allow_merge_commit: allowMergeCommit,
|
||||
allow_squash_merge: allowSquashMerge,
|
||||
allow_rebase_merge: allowRebaseMerge,
|
||||
})
|
||||
: client.rest.repos.createForAuthenticatedUser({
|
||||
name: repo,
|
||||
private: repoVisibility === 'private',
|
||||
description: description,
|
||||
delete_branch_on_merge: deleteBranchOnMerge,
|
||||
allow_merge_commit: allowMergeCommit,
|
||||
allow_squash_merge: allowSquashMerge,
|
||||
allow_rebase_merge: allowRebaseMerge,
|
||||
});
|
||||
|
||||
let newRepo;
|
||||
|
||||
try {
|
||||
newRepo = (await repoCreationPromise).data;
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
if (e.message === 'Resource not accessible by integration') {
|
||||
ctx.logger.warn(
|
||||
`The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`,
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
`Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (access?.startsWith(`${owner}/`)) {
|
||||
const [, team] = access.split('/');
|
||||
await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
|
||||
org: owner,
|
||||
team_slug: team,
|
||||
owner,
|
||||
repo,
|
||||
permission: 'admin',
|
||||
});
|
||||
// No need to add access if it's the person who owns the personal account
|
||||
} else if (access && access !== owner) {
|
||||
await client.rest.repos.addCollaborator({
|
||||
owner,
|
||||
repo,
|
||||
username: access,
|
||||
permission: 'admin',
|
||||
});
|
||||
}
|
||||
|
||||
if (collaborators) {
|
||||
for (const collaborator of collaborators) {
|
||||
try {
|
||||
if ('user' in collaborator) {
|
||||
await client.rest.repos.addCollaborator({
|
||||
owner,
|
||||
repo,
|
||||
username: collaborator.user,
|
||||
permission: collaborator.access,
|
||||
});
|
||||
} else if ('username' in collaborator) {
|
||||
ctx.logger.warn(
|
||||
'The field `username` is deprecated in favor of `team` and will be removed in the future.',
|
||||
);
|
||||
await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
|
||||
org: owner,
|
||||
team_slug: collaborator.username,
|
||||
owner,
|
||||
repo,
|
||||
permission: collaborator.access,
|
||||
});
|
||||
} else if ('team' in collaborator) {
|
||||
await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
|
||||
org: owner,
|
||||
team_slug: collaborator.team,
|
||||
owner,
|
||||
repo,
|
||||
permission: collaborator.access,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
const name = extractCollaboratorName(collaborator);
|
||||
ctx.logger.warn(
|
||||
`Skipping ${collaborator.access} access for ${name}, ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (topics) {
|
||||
try {
|
||||
await client.rest.repos.replaceAllTopics({
|
||||
owner,
|
||||
repo,
|
||||
names: topics.map(t => t.toLowerCase()),
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
ctx.logger.warn(`Skipping topics ${topics.join(' ')}, ${e.message}`);
|
||||
}
|
||||
}
|
||||
const newRepo = await createGithubRepoWithCollaboratorsAndTopics(
|
||||
client,
|
||||
repo,
|
||||
owner,
|
||||
repoVisibility,
|
||||
description,
|
||||
deleteBranchOnMerge,
|
||||
allowMergeCommit,
|
||||
allowSquashMerge,
|
||||
allowRebaseMerge,
|
||||
access,
|
||||
collaborators,
|
||||
topics,
|
||||
ctx.logger,
|
||||
);
|
||||
|
||||
const remoteUrl = newRepo.clone_url;
|
||||
const repoContentsUrl = `${newRepo.html_url}/blob/${defaultBranch}`;
|
||||
|
||||
const gitAuthorInfo = {
|
||||
name: gitAuthorName
|
||||
? gitAuthorName
|
||||
: config.getOptionalString('scaffolder.defaultAuthor.name'),
|
||||
email: gitAuthorEmail
|
||||
? gitAuthorEmail
|
||||
: config.getOptionalString('scaffolder.defaultAuthor.email'),
|
||||
};
|
||||
// const gitAuthorInfo = {
|
||||
// name: gitAuthorName
|
||||
// ? gitAuthorName
|
||||
// : config.getOptionalString('scaffolder.defaultAuthor.name'),
|
||||
// email: gitAuthorEmail
|
||||
// ? gitAuthorEmail
|
||||
// : config.getOptionalString('scaffolder.defaultAuthor.email'),
|
||||
// };
|
||||
|
||||
await initRepoAndPush({
|
||||
dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
|
||||
// await initRepoAndPush({
|
||||
// dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
|
||||
// remoteUrl,
|
||||
// defaultBranch,
|
||||
// auth: {
|
||||
// username: 'x-access-token',
|
||||
// password: octokitOptions.auth,
|
||||
// },
|
||||
// logger: ctx.logger,
|
||||
// commitMessage: gitCommitMessage
|
||||
// ? gitCommitMessage
|
||||
// : config.getOptionalString('scaffolder.defaultCommitMessage'),
|
||||
// gitAuthorInfo,
|
||||
// });
|
||||
|
||||
// if (protectDefaultBranch) {
|
||||
// try {
|
||||
// await enableBranchProtectionOnDefaultRepoBranch({
|
||||
// owner,
|
||||
// client,
|
||||
// repoName: newRepo.name,
|
||||
// logger: ctx.logger,
|
||||
// defaultBranch,
|
||||
// requireCodeOwnerReviews,
|
||||
// requiredStatusCheckContexts,
|
||||
// });
|
||||
// } catch (e) {
|
||||
// assertError(e);
|
||||
// ctx.logger.warn(
|
||||
// `Skipping: default branch protection on '${newRepo.name}', ${e.message}`,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
await initRepoPushAndProtect(
|
||||
remoteUrl,
|
||||
octokitOptions.auth,
|
||||
ctx.workspacePath,
|
||||
ctx.input.sourcePath,
|
||||
defaultBranch,
|
||||
auth: {
|
||||
username: 'x-access-token',
|
||||
password: octokitOptions.auth,
|
||||
},
|
||||
logger: ctx.logger,
|
||||
commitMessage: gitCommitMessage
|
||||
? gitCommitMessage
|
||||
: config.getOptionalString('scaffolder.defaultCommitMessage'),
|
||||
gitAuthorInfo,
|
||||
});
|
||||
|
||||
if (protectDefaultBranch) {
|
||||
try {
|
||||
await enableBranchProtectionOnDefaultRepoBranch({
|
||||
owner,
|
||||
client,
|
||||
repoName: newRepo.name,
|
||||
logger: ctx.logger,
|
||||
defaultBranch,
|
||||
requireCodeOwnerReviews,
|
||||
requiredStatusCheckContexts,
|
||||
});
|
||||
} catch (e) {
|
||||
assertError(e);
|
||||
ctx.logger.warn(
|
||||
`Skipping: default branch protection on '${newRepo.name}', ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
protectDefaultBranch,
|
||||
owner,
|
||||
client,
|
||||
repo,
|
||||
requireCodeOwnerReviews,
|
||||
requiredStatusCheckContexts,
|
||||
config,
|
||||
ctx.logger,
|
||||
gitCommitMessage,
|
||||
gitAuthorName,
|
||||
gitAuthorEmail,
|
||||
);
|
||||
|
||||
ctx.output('remoteUrl', remoteUrl);
|
||||
ctx.output('repoContentsUrl', repoContentsUrl);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function extractCollaboratorName(
|
||||
collaborator: { user: string } | { team: string } | { username: string },
|
||||
) {
|
||||
if ('username' in collaborator) return collaborator.username;
|
||||
if ('user' in collaborator) return collaborator.user;
|
||||
return collaborator.team;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user