diff --git a/.changeset/strong-sloths-push.md b/.changeset/strong-sloths-push.md new file mode 100644 index 0000000000..5e449b21ef --- /dev/null +++ b/.changeset/strong-sloths-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Allow using globby's negative matching with copyWithoutTemplating/copyWithoutRender. Allows including an entire subdirectory while excluding a single file so that it will still be templated instead of needing to list every other file and ensure the list is updated when new files are added. diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index d396a00c26..119cd78eb7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -470,6 +470,57 @@ describe('fetch:template', () => { ), ).resolves.toEqual('1234'); }); + + describe('with exclusion filter', () => { + beforeEach(async () => { + context = mockContext({ + values: { + name: 'test-project', + count: 1234, + }, + copyWithoutTemplating: [ + '.unprocessed', + '!*/templated-process-content-${{ values.name }}.txt', + ], + }); + + mockFetchContents.mockImplementation(({ outputPath }) => { + mockDir.setContent({ + [outputPath]: { + processed: { + 'templated-content-${{ values.name }}.txt': + '${{ values.count }}', + }, + '.unprocessed': { + 'templated-content-${{ values.name }}.txt': + '${{ values.count }}', + 'templated-process-content-${{ values.name }}.txt': + '${{ values.count }}', + }, + }, + }); + + return Promise.resolve(); + }); + + await action.handler(context); + }); + + it('renders path template including excluded matches in copyWithoutTemplating', async () => { + await expect( + fs.readFile( + `${workspacePath}/target/.unprocessed/templated-process-content-test-project.txt`, + 'utf-8', + ), + ).resolves.toEqual('1234'); + await expect( + fs.readFile( + `${workspacePath}/target/.unprocessed/templated-content-test-project.txt`, + 'utf-8', + ), + ).resolves.toEqual('${{ values.count }}'); + }); + }); }); describe('cookiecutter compatibility mode', () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 1116d4dd5d..26b249b76d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -207,19 +207,13 @@ export function createFetchTemplateAction(options: { }); const nonTemplatedEntries = new Set( - ( - await Promise.all( - (copyOnlyPatterns || []).map(pattern => - globby(pattern, { - cwd: templateDir, - dot: true, - onlyFiles: false, - markDirectories: true, - followSymbolicLinks: false, - }), - ), - ) - ).flat(), + await globby(copyOnlyPatterns || [], { + cwd: templateDir, + dot: true, + onlyFiles: false, + markDirectories: true, + followSymbolicLinks: false, + }), ); // Cookiecutter prefixes all parameters in templates with