diff --git a/.changeset/fair-peaches-learn.md b/.changeset/fair-peaches-learn.md new file mode 100644 index 0000000000..772980ee57 --- /dev/null +++ b/.changeset/fair-peaches-learn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Allow listing file contents with `debug:log` scaffolder action diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 00f690c646..3d84c30bcf 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -130,7 +130,7 @@ export function createCatalogWriteAction(): TemplateAction_2< export function createDebugLogAction(): TemplateAction_2< { message?: string | undefined; - listWorkspace?: boolean | undefined; + listWorkspace?: boolean | 'with-contents' | 'with-filenames' | undefined; }, JsonObject >; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.examples.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.examples.ts index 0e9173bfa6..c1ad6a2982 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.examples.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.examples.ts @@ -48,4 +48,19 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'List the workspace directory with file contents', + example: yaml.stringify({ + steps: [ + { + action: 'debug:log', + id: 'write-workspace-directory', + name: 'List the workspace directory with file contents', + input: { + listWorkspace: 'with-contents', + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.test.ts index 9c626f1dfe..4606a01e0c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.test.ts @@ -35,8 +35,9 @@ describe('debug:log', () => { beforeEach(() => { mockDir.setContent({ - [`${mockContext.workspacePath}/README.md`]: '', - [`${mockContext.workspacePath}/a-directory/index.md`]: '', + [`${mockContext.workspacePath}/README.md`]: 'This is a README file', + [`${mockContext.workspacePath}/a-directory/index.md`]: + 'This is a markdown file', }); jest.resetAllMocks(); }); @@ -94,6 +95,34 @@ describe('debug:log', () => { ); }); + it('should log the workspace content with file contents from an example, if active', async () => { + const example = action.examples?.find( + sample => + sample.description === + 'List the workspace directory with file contents', + )?.example as string; + expect(typeof example).toEqual('string'); + const context = { + ...mockContext, + ...yaml.parse(example).steps[0], + }; + + await action.handler(context); + + expect(logger.info).toHaveBeenCalledWith( + expect.stringContaining('README.md'), + ); + expect(logger.info).toHaveBeenCalledWith( + expect.stringContaining(join('a-directory', 'index.md')), + ); + expect(logger.info).toHaveBeenCalledWith( + expect.stringContaining('This is a README file'), + ); + expect(logger.info).toHaveBeenCalledWith( + expect.stringContaining('This is a markdown file'), + ); + }); + it('should log message from an example', async () => { const example = action.examples?.find( sample => sample.description === 'Write a debug message', diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts index 98ab02437e..2ff64e1025 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts @@ -15,9 +15,11 @@ */ import { readdir, stat } from 'fs-extra'; -import { relative, join } from 'path'; +import { join, relative } from 'path'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { examples } from './log.examples'; +import fs from 'fs'; +import { z } from 'zod'; const id = 'debug:log'; @@ -32,28 +34,24 @@ const id = 'debug:log'; * @public */ export function createDebugLogAction() { - return createTemplateAction<{ message?: string; listWorkspace?: boolean }>({ + return createTemplateAction<{ + message?: string; + listWorkspace?: boolean | 'with-filenames' | 'with-contents'; + }>({ id, description: - 'Writes a message into the log or lists all files in the workspace.', + 'Writes a message into the log and/or lists all files in the workspace.', examples, schema: { - input: { - type: 'object', - properties: { - message: { - title: 'Message to output.', - type: 'string', - }, - listWorkspace: { - title: 'List all files in the workspace, if true.', - type: 'boolean', - }, - extra: { - title: 'Extra info', - }, - }, - }, + input: z.object({ + message: z.string({ description: 'Message to output.' }).optional(), + listWorkspace: z + .union([z.boolean(), z.enum(['with-filenames', 'with-contents'])], { + description: + 'List all files in the workspace. If used with "with-contents", also the file contents are listed.', + }) + .optional(), + }), }, supportsDryRun: true, async handler(ctx) { @@ -67,7 +65,14 @@ export function createDebugLogAction() { const files = await recursiveReadDir(ctx.workspacePath); ctx.logger.info( `Workspace:\n${files - .map(f => ` - ${relative(ctx.workspacePath, f)}`) + .map(f => { + const relativePath = relative(ctx.workspacePath, f); + if (ctx.input?.listWorkspace === 'with-contents') { + const content = fs.readFileSync(f, 'utf-8'); + return ` - ${relativePath}:\n\n ${content}`; + } + return ` - ${relativePath}`; + }) .join('\n')}`, ); }