Merge pull request #30252 from fearphage/allow-file-deletion
feat(scaffolder-github): Allow file deletion in pull requests
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-github': patch
|
||||
---
|
||||
|
||||
Added support for file deletion to `publish:github:pull-request` action.
|
||||
|
||||
Example usage:
|
||||
|
||||
```diff
|
||||
- action: publish:github:pull-request
|
||||
id: clean-up-pr
|
||||
input:
|
||||
description: This is the description
|
||||
+ filesToDelete:
|
||||
+ - outdated/changelog.md
|
||||
+ - sample-file.txt
|
||||
owner: owner
|
||||
repo: repo
|
||||
title: Title Goes Here
|
||||
|
||||
```
|
||||
@@ -446,6 +446,7 @@ export const createPublishGithubPullRequestAction: (
|
||||
branchName: string;
|
||||
title: string;
|
||||
description: string;
|
||||
filesToDelete?: string[] | undefined;
|
||||
targetBranchName?: string | undefined;
|
||||
draft?: boolean | undefined;
|
||||
sourcePath?: string | undefined;
|
||||
|
||||
@@ -24,9 +24,11 @@ import {
|
||||
TemplateAction,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'node:path';
|
||||
import { createPublishGithubPullRequestAction } from './githubPullRequest';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils';
|
||||
import { DELETE_FILE } from 'octokit-plugin-create-pull-request';
|
||||
|
||||
type GithubPullRequestActionInput = ReturnType<
|
||||
typeof createPublishGithubPullRequestAction
|
||||
@@ -50,8 +52,6 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
const workspacePath = mockDir.resolve('workspace');
|
||||
|
||||
beforeEach(() => {
|
||||
mockDir.clear();
|
||||
|
||||
config = new ConfigReader({});
|
||||
integrations = ScmIntegrations.fromConfig(config);
|
||||
fakeClient = {
|
||||
@@ -92,6 +92,7 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockDir.clear();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
@@ -304,6 +305,118 @@ describe('createPublishGithubPullRequestAction', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('with filesToDelete', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
input = {
|
||||
filesToDelete: ['changed-file-to-delete.txt', 'delete-me-too.md'],
|
||||
repoUrl: 'github.com?owner=myorg&repo=myrepo',
|
||||
title: 'Create my new app',
|
||||
branchName: 'new-app',
|
||||
description: 'This PR is really good',
|
||||
};
|
||||
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
'catpants.md': 'cat + pants',
|
||||
'changed-file-to-delete.txt': 'file is changed and deleted',
|
||||
'foobar.txt': 'Hello there!',
|
||||
},
|
||||
});
|
||||
|
||||
ctx = createMockActionContext({ input, workspacePath });
|
||||
});
|
||||
|
||||
it('should delete named files', async () => {
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(fakeClient.createPullRequest).toHaveBeenCalledWith({
|
||||
owner: 'myorg',
|
||||
repo: 'myrepo',
|
||||
title: input.title,
|
||||
head: input.branchName,
|
||||
body: input.description,
|
||||
changes: [
|
||||
{
|
||||
commit: input.title,
|
||||
files: {
|
||||
'catpants.md': {
|
||||
content: Buffer.from('cat + pants').toString('base64'),
|
||||
encoding: 'base64',
|
||||
mode: '100644',
|
||||
},
|
||||
'foobar.txt': {
|
||||
content: Buffer.from('Hello there!').toString('base64'),
|
||||
encoding: 'base64',
|
||||
mode: '100644',
|
||||
},
|
||||
'changed-file-to-delete.txt': DELETE_FILE,
|
||||
'delete-me-too.md': DELETE_FILE,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe('with targetPath', () => {
|
||||
const targetPath = `target-path-${Date.now()}`;
|
||||
|
||||
beforeEach(() => {
|
||||
Object.assign(input, {
|
||||
filesToDelete: [
|
||||
path.posix.join('nested', 'catpants.md'),
|
||||
path.posix.join('nested', 'delete-me.too'),
|
||||
],
|
||||
targetPath,
|
||||
});
|
||||
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
'catpants.md': 'cat + pants',
|
||||
'foobar.txt': 'Hello there!',
|
||||
[path.posix.join('nested', 'catpants.md')]: 'delete me',
|
||||
[path.posix.join('nested', 'delete-me.too')]: 'delete me too',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete named files', async () => {
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(fakeClient.createPullRequest).toHaveBeenCalledWith({
|
||||
owner: 'myorg',
|
||||
repo: 'myrepo',
|
||||
title: input.title,
|
||||
head: input.branchName,
|
||||
body: input.description,
|
||||
changes: [
|
||||
{
|
||||
commit: input.title,
|
||||
files: {
|
||||
[path.posix.join(targetPath, 'catpants.md')]: {
|
||||
content: Buffer.from('cat + pants').toString('base64'),
|
||||
encoding: 'base64',
|
||||
mode: '100644',
|
||||
},
|
||||
[path.posix.join(targetPath, 'foobar.txt')]: {
|
||||
content: Buffer.from('Hello there!').toString('base64'),
|
||||
encoding: 'base64',
|
||||
mode: '100644',
|
||||
},
|
||||
[path.posix.join(targetPath, 'nested', 'catpants.md')]:
|
||||
DELETE_FILE,
|
||||
[path.posix.join(targetPath, 'nested', 'delete-me.too')]:
|
||||
DELETE_FILE,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with repoUrl', () => {
|
||||
let input: GithubPullRequestActionInput;
|
||||
let ctx: ActionContext<GithubPullRequestActionInput, any, any>;
|
||||
|
||||
@@ -27,7 +27,10 @@ import {
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import { Octokit } from 'octokit';
|
||||
import { CustomErrorBase, InputError } from '@backstage/errors';
|
||||
import { createPullRequest } from 'octokit-plugin-create-pull-request';
|
||||
import {
|
||||
createPullRequest,
|
||||
DELETE_FILE,
|
||||
} from 'octokit-plugin-create-pull-request';
|
||||
import { getOctokitOptions } from '../util';
|
||||
import { examples } from './githubPullRequest.examples';
|
||||
import {
|
||||
@@ -143,6 +146,12 @@ export const createPublishGithubPullRequestAction = (
|
||||
z.string({
|
||||
description: 'The name for the branch',
|
||||
}),
|
||||
filesToDelete: z =>
|
||||
z
|
||||
.array(z.string(), {
|
||||
description: 'List of files that will be deleted',
|
||||
})
|
||||
.optional(),
|
||||
targetBranchName: z =>
|
||||
z
|
||||
.string({
|
||||
@@ -269,6 +278,7 @@ export const createPublishGithubPullRequestAction = (
|
||||
const {
|
||||
repoUrl,
|
||||
branchName,
|
||||
filesToDelete,
|
||||
targetBranchName,
|
||||
title,
|
||||
description,
|
||||
@@ -323,8 +333,8 @@ export const createPublishGithubPullRequestAction = (
|
||||
file: SerializedFile,
|
||||
): 'utf-8' | 'base64' => (file.symlink ? 'utf-8' : 'base64');
|
||||
|
||||
const files = Object.fromEntries(
|
||||
directoryContents.map(file => [
|
||||
const files = Object.fromEntries([
|
||||
...directoryContents.map(file => [
|
||||
targetPath ? path.posix.join(targetPath, file.path) : file.path,
|
||||
{
|
||||
// See the properties of tree items
|
||||
@@ -341,7 +351,13 @@ export const createPublishGithubPullRequestAction = (
|
||||
content: file.content.toString(determineFileEncoding(file)),
|
||||
},
|
||||
]),
|
||||
);
|
||||
// order of arrays is important so filesToDelete will overwrite
|
||||
// changes from files above
|
||||
...(filesToDelete || []).map(filePath => [
|
||||
targetPath ? path.posix.join(targetPath, filePath) : filePath,
|
||||
DELETE_FILE,
|
||||
]),
|
||||
]);
|
||||
|
||||
// If this is a dry run, log and return
|
||||
if (ctx.isDryRun) {
|
||||
|
||||
Reference in New Issue
Block a user