diff --git a/.changeset/proud-dolls-behave.md b/.changeset/proud-dolls-behave.md new file mode 100644 index 0000000000..8a93bdb4bf --- /dev/null +++ b/.changeset/proud-dolls-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Support file globbing in fs:delete diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 936b0ec8b6..253a8feb0a 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -157,6 +157,7 @@ Gitiles gitlab GitLab globals +globbing Gource Grafana graphql diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.examples.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.examples.ts index 8aea076b9d..a32249f210 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.examples.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.examples.ts @@ -33,4 +33,19 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Delete files with wildcard', + example: yaml.stringify({ + steps: [ + { + action: 'fs:delete', + id: 'deleteFiles', + name: 'Delete files', + input: { + files: ['*.txt'], + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts index 8226133376..f037323c04 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts @@ -95,6 +95,15 @@ describe('fs:delete', () => { ).rejects.toThrow( /Relative path is not allowed to refer to a directory outside its parent/, ); + + await expect( + action.handler({ + ...mockContext, + input: { files: ['../../../**/index.js'] }, + }), + ).rejects.toThrow( + /Relative path is not allowed to refer to a directory outside its parent/, + ); }); it('should call fs.rm with the correct values', async () => { @@ -114,4 +123,25 @@ describe('fs:delete', () => { expect(fileExists).toBe(false); }); }); + + it('should handle wildcards', async () => { + const files = ['unit-test-a.js', 'unit-test-b.js']; + + files.forEach(file => { + const filePath = resolvePath(workspacePath, file); + const fileExists = fs.existsSync(filePath); + expect(fileExists).toBe(true); + }); + + await action.handler({ + ...mockContext, + input: { files: ['unit-*.js'] }, + }); + + files.forEach(file => { + const filePath = resolvePath(workspacePath, file); + const fileExists = fs.existsSync(filePath); + expect(fileExists).toBe(false); + }); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts index 13dfc69525..5a06c7d2aa 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts @@ -18,6 +18,7 @@ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { InputError } from '@backstage/errors'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import fs from 'fs-extra'; +import globby from 'globby'; import { examples } from './delete.examples'; /** @@ -52,14 +53,20 @@ export const createFilesystemDeleteAction = () => { } for (const file of ctx.input.files) { - const filepath = resolveSafeChildPath(ctx.workspacePath, file); + const safeFilepath = resolveSafeChildPath(ctx.workspacePath, file); + const resolvedPaths = await globby(safeFilepath, { + cwd: ctx.workspacePath, + absolute: true, + }); - try { - await fs.remove(filepath); - ctx.logger.info(`File ${filepath} deleted successfully`); - } catch (err) { - ctx.logger.error(`Failed to delete file ${filepath}:`, err); - throw err; + for (const filepath of resolvedPaths) { + try { + await fs.remove(filepath); + ctx.logger.info(`File ${filepath} deleted successfully`); + } catch (err) { + ctx.logger.error(`Failed to delete file ${filepath}:`, err); + throw err; + } } } },