@armyknife/backstage-github-template
Version:
GitHub repository template creation for Backstage
172 lines (171 loc) • 8.19 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createGitHubRepoAction = void 0;
const plugin_scaffolder_backend_1 = require("@backstage/plugin-scaffolder-backend");
const rest_1 = require("@octokit/rest");
function createGitHubRepoAction(options) {
const { integrations, config, catalogClient, reader, logger } = options;
return (0, plugin_scaffolder_backend_1.createTemplateAction)({
id: 'armyknife:github:create-repo',
description: 'Creates a GitHub repository with security best practices',
schema: {
input: {
type: 'object',
required: ['name', 'owner', 'description'],
properties: {
name: {
type: 'string',
title: 'Repository Name',
description: 'Name of the repository',
},
owner: {
type: 'string',
title: 'Repository Owner',
description: 'Owner of the repository (organization or user)',
},
description: {
type: 'string',
title: 'Description',
description: 'Description of the repository',
},
visibility: {
type: 'string',
enum: ['public', 'private', 'internal'],
default: 'private',
title: 'Repository Visibility',
description: 'Visibility of the repository',
},
enableSecurityFeatures: {
type: 'boolean',
default: true,
title: 'Enable Security Features',
description: 'Enable security features like branch protection, CODEOWNERS, etc.',
},
token: {
type: 'string',
title: 'GitHub Token',
description: 'GitHub token to use for repository creation. If not provided, will use the token from app-config.yaml',
},
defaultBranch: {
type: 'string',
title: 'Default Branch',
description: 'Default branch for the repository',
default: 'main',
},
},
},
output: {
type: 'object',
properties: {
remoteUrl: {
type: 'string',
title: 'Remote URL',
description: 'URL to the repository',
},
repoContentsUrl: {
type: 'string',
title: 'Repository Contents URL',
description: 'URL to the repository contents',
},
},
},
},
async handler(ctx) {
const { name, owner, description, visibility = 'private', enableSecurityFeatures = true, token, defaultBranch = 'main', } = ctx.input;
// Get GitHub token from input or config
const githubToken = token || config.getOptionalString('integrations.github.token');
if (!githubToken) {
throw new Error('No GitHub token provided in action input or app-config.yaml');
}
// Initialize GitHub client
const octokit = new rest_1.Octokit({
auth: githubToken,
});
try {
// Create repository
const createRepoResponse = await octokit.repos.createInOrg({
org: owner,
name,
description,
private: visibility !== 'public',
auto_init: true,
delete_branch_on_merge: true,
allow_squash_merge: true,
allow_merge_commit: false,
allow_rebase_merge: true,
}).catch(async (error) => {
// If org creation fails, try as user
if (error.status === 404) {
return await octokit.repos.createForAuthenticatedUser({
name,
description,
private: visibility !== 'public',
auto_init: true,
delete_branch_on_merge: true,
allow_squash_merge: true,
allow_merge_commit: false,
allow_rebase_merge: true,
});
}
throw error;
});
const repoFullName = createRepoResponse.data.full_name;
const repoUrl = createRepoResponse.data.html_url;
const defaultBranchRef = `heads/${defaultBranch}`;
ctx.logger.info(`Created GitHub repository: ${repoFullName}`);
// If security features are enabled, set up branch protection
if (enableSecurityFeatures) {
try {
// Add CODEOWNERS file
await octokit.repos.createOrUpdateFileContents({
owner: createRepoResponse.data.owner.login,
repo: name,
path: '.github/CODEOWNERS',
message: 'Add CODEOWNERS file',
content: Buffer.from('# Default owners for everything\n* @' + owner).toString('base64'),
branch: defaultBranch,
});
// Add security policy
await octokit.repos.createOrUpdateFileContents({
owner: createRepoResponse.data.owner.login,
repo: name,
path: 'SECURITY.md',
message: 'Add security policy',
content: Buffer.from('# Security Policy\n\n## Reporting a Vulnerability\n\nPlease report security vulnerabilities to security@example.com.').toString('base64'),
branch: defaultBranch,
});
// Set up branch protection
await octokit.repos.updateBranchProtection({
owner: createRepoResponse.data.owner.login,
repo: name,
branch: defaultBranch,
required_status_checks: {
strict: true,
contexts: ['continuous-integration/ci-checks'],
},
enforce_admins: true,
required_pull_request_reviews: {
dismiss_stale_reviews: true,
require_code_owner_reviews: true,
required_approving_review_count: 1,
},
restrictions: null,
});
ctx.logger.info(`Set up security features for ${repoFullName}`);
}
catch (error) {
ctx.logger.error(`Failed to set up security features: ${error.message}`);
}
}
// Set output
ctx.output('remoteUrl', repoUrl);
ctx.output('repoContentsUrl', `${repoUrl}/blob/${defaultBranch}`);
}
catch (error) {
ctx.logger.error(`Failed to create GitHub repository: ${error.message}`);
throw error;
}
},
});
}
exports.createGitHubRepoAction = createGitHubRepoAction;