From aee107ba37e3abc50c61123901811841f9f6d629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20=C3=96st=C3=B6r?= Date: Wed, 10 Sep 2025 17:38:03 +0100 Subject: [PATCH] feat(scaffolder): add auto_init option for repository creation and examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Adam Östör --- .changeset/brown-pens-shave.md | 18 +++ .../report.api.md | 1 + .../actions/githubRepoCreate.examples.test.ts | 101 ++++++++++++++ .../src/actions/githubRepoCreate.examples.ts | 15 ++ .../src/actions/githubRepoCreate.test.ts | 128 ++++++++++++++++++ .../src/actions/githubRepoCreate.ts | 3 + .../src/actions/helpers.ts | 3 + .../src/actions/inputProperties.ts | 9 ++ 8 files changed, 278 insertions(+) create mode 100644 .changeset/brown-pens-shave.md diff --git a/.changeset/brown-pens-shave.md b/.changeset/brown-pens-shave.md new file mode 100644 index 0000000000..33cf7cfeca --- /dev/null +++ b/.changeset/brown-pens-shave.md @@ -0,0 +1,18 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Add `auto_init` option to 'github:repo:create' action to create repository with an initial commit containing a README.md file + +This initial commit is created by GitHub itself and the commit is signed, so the repository will not be empty after creation. + +```diff + - action: github:repo:create + id: init-new-repo + input: + repoUrl: 'github.com?repo=repo&owner=owner' + description: This is the description + visibility: private ++ autoInit: true + +``` diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index 8f1bc23785..ed109eb9e3 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -291,6 +291,7 @@ export function createGithubRepoCreateAction(options: { requiredLinearHistory?: boolean | undefined; customProperties?: Record | undefined; subscribe?: boolean | undefined; + autoInit?: boolean | undefined; }, { remoteUrl: string; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts index da4837adb7..c37e9c222b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts @@ -136,6 +136,7 @@ describe('github:repo:create examples', () => { has_wiki: undefined, homepage: undefined, visibility: 'private', + auto_init: undefined, }); }); @@ -169,6 +170,7 @@ describe('github:repo:create examples', () => { has_wiki: undefined, homepage: undefined, visibility: 'private', + auto_init: undefined, }); }); @@ -203,6 +205,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: false, // disable wiki homepage: undefined, + auto_init: undefined, }); }); @@ -246,6 +249,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: 'https://example.com', + auto_init: undefined, }); }); @@ -289,6 +293,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -332,6 +337,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -375,6 +381,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -418,6 +425,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -461,6 +469,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -504,6 +513,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -547,6 +557,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -590,6 +601,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -633,6 +645,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -676,6 +689,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -719,6 +733,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -762,6 +777,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -805,6 +821,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -848,6 +865,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -891,6 +909,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -934,6 +953,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -977,6 +997,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: 'https://project-xyz.com', + auto_init: undefined, }); }); @@ -1020,6 +1041,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1063,6 +1085,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1106,6 +1129,7 @@ describe('github:repo:create examples', () => { has_projects: true, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1149,6 +1173,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1192,6 +1217,7 @@ describe('github:repo:create examples', () => { has_projects: false, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1235,6 +1261,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1278,6 +1305,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1321,6 +1349,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1364,6 +1393,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1407,6 +1437,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1450,6 +1481,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1493,6 +1525,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1536,6 +1569,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1579,6 +1613,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: 'https://internal.example.com', + auto_init: undefined, }); }); @@ -1622,6 +1657,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1665,6 +1701,7 @@ describe('github:repo:create examples', () => { has_projects: true, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1708,6 +1745,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1751,6 +1789,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1794,6 +1833,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1837,6 +1877,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: 'https://webapp.example.com', + auto_init: undefined, }); }); @@ -1880,6 +1921,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1923,6 +1965,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -1966,6 +2009,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2009,6 +2053,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2052,6 +2097,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2095,6 +2141,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: false, homepage: undefined, + auto_init: undefined, }); }); @@ -2138,6 +2185,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2181,6 +2229,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2224,6 +2273,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2267,6 +2317,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2310,6 +2361,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: 'https://example.com', + auto_init: undefined, }); }); @@ -2353,6 +2405,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2396,6 +2449,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2439,6 +2493,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2482,6 +2537,7 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); }); @@ -2525,6 +2581,51 @@ describe('github:repo:create examples', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, + }); + }); + + it(`Should ${examples[58].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[58].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + allow_update_branch: false, + custom_properties: undefined, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + auto_init: true, }); }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts index ce39e7510d..41c6a15fba 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts @@ -1006,4 +1006,19 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Create a repository with an initial commit.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with an initial (signed) commit containing a README', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + autoInit: true, + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index b90f0c4e5a..adad5237da 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -157,6 +157,7 @@ describe('github:repo:create', () => { has_wiki: undefined, homepage: undefined, visibility: 'private', + auto_init: undefined, }); await action.handler({ @@ -185,6 +186,7 @@ describe('github:repo:create', () => { has_wiki: undefined, homepage: undefined, visibility: 'public', + auto_init: undefined, }); await action.handler({ @@ -212,6 +214,7 @@ describe('github:repo:create', () => { has_projects: undefined, has_wiki: undefined, visibility: 'private', + auto_init: undefined, }); await action.handler({ @@ -242,6 +245,7 @@ describe('github:repo:create', () => { has_projects: undefined, has_issues: undefined, homepage: 'https://example.com', + auto_init: undefined, }); await action.handler({ @@ -271,6 +275,7 @@ describe('github:repo:create', () => { has_wiki: undefined, has_projects: undefined, has_issues: undefined, + auto_init: undefined, }); await action.handler({ @@ -303,6 +308,67 @@ describe('github:repo:create', () => { has_wiki: undefined, homepage: 'https://example.com', visibility: 'private', + auto_init: undefined, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + autoInit: true, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + description: 'description', + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + allow_update_branch: false, + custom_properties: undefined, + has_issues: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + visibility: 'private', + auto_init: true, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + autoInit: false, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + description: 'description', + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + allow_update_branch: false, + custom_properties: undefined, + has_issues: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + visibility: 'private', + auto_init: false, }); }); @@ -334,6 +400,7 @@ describe('github:repo:create', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); await action.handler({ @@ -361,6 +428,7 @@ describe('github:repo:create', () => { has_projects: undefined, has_wiki: undefined, homepage: undefined, + auto_init: undefined, }); await action.handler({ @@ -388,6 +456,7 @@ describe('github:repo:create', () => { has_issues: undefined, has_projects: undefined, has_wiki: undefined, + auto_init: undefined, }); await action.handler({ @@ -417,6 +486,7 @@ describe('github:repo:create', () => { has_projects: undefined, has_issues: undefined, homepage: 'https://example.com', + auto_init: undefined, }); await action.handler({ @@ -446,6 +516,7 @@ describe('github:repo:create', () => { has_projects: undefined, has_issues: undefined, homepage: 'https://example.com', + auto_init: undefined, }); // Custom properties on user repos should be ignored @@ -478,6 +549,63 @@ describe('github:repo:create', () => { has_projects: undefined, has_wiki: undefined, homepage: 'https://example.com', + auto_init: undefined, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + autoInit: true, + }, + }); + expect( + mockOctokit.rest.repos.createForAuthenticatedUser, + ).toHaveBeenCalledWith({ + description: 'description', + name: 'repo', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_message: 'COMMIT_MESSAGES', + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + allow_update_branch: false, + has_wiki: undefined, + has_projects: undefined, + has_issues: undefined, + homepage: undefined, + auto_init: true, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + autoInit: false, + }, + }); + expect( + mockOctokit.rest.repos.createForAuthenticatedUser, + ).toHaveBeenCalledWith({ + description: 'description', + name: 'repo', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_message: 'COMMIT_MESSAGES', + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + allow_update_branch: false, + has_wiki: undefined, + has_projects: undefined, + has_issues: undefined, + homepage: undefined, + auto_init: false, }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts index a85010797d..952bf514d4 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts @@ -81,6 +81,7 @@ export function createGithubRepoCreateAction(options: { requiredLinearHistory: inputProps.requiredLinearHistory, customProperties: inputProps.customProperties, subscribe: inputProps.subscribe, + autoInit: inputProps.autoInit, }, output: { remoteUrl: outputProps.remoteUrl, @@ -113,6 +114,7 @@ export function createGithubRepoCreateAction(options: { customProperties, subscribe, token: providedToken, + autoInit = undefined, } = ctx.input; const { host, owner, repo } = parseRepoUrl(repoUrl, integrations); @@ -164,6 +166,7 @@ export function createGithubRepoCreateAction(options: { customProperties, subscribe, ctx.logger, + autoInit, ); return newRepo.clone_url; }, diff --git a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts index 6509d7fd29..a3d0caed33 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts @@ -78,6 +78,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics( customProperties: { [key: string]: string | string[] } | undefined, subscribe: boolean | undefined, logger: LoggerService, + autoInit?: boolean | undefined, ) { // eslint-disable-next-line testing-library/no-await-sync-queries const user = await client.rest.users.getByUsername({ @@ -109,6 +110,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics( has_projects: hasProjects, has_wiki: hasWiki, has_issues: hasIssues, + auto_init: autoInit, // Custom properties only available on org repos custom_properties: customProperties, }) @@ -128,6 +130,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics( has_projects: hasProjects, has_wiki: hasWiki, has_issues: hasIssues, + auto_init: autoInit, }); let newRepo; diff --git a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts index bfa232c45d..8a901b0e99 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts @@ -413,6 +413,14 @@ const branch = (z: typeof zod) => }) .optional(); +const autoInit = (z: typeof zod) => + z + .boolean({ + description: `Create an initial commit with empty README. Default is 'false'`, + }) + .default(false) + .optional(); + export { repoUrl, description, @@ -458,4 +466,5 @@ export { bypassPullRequestAllowances, branch, blockCreations, + autoInit, };