From 77bd57607c72a40e532a4d30dbb576490e6642de Mon Sep 17 00:00:00 2001 From: jmrt Date: Thu, 18 Jul 2024 16:39:57 +0200 Subject: [PATCH 1/6] feat: Convert ansi output to html to add it to the PR. Add config for maximum number of output characters. --- README.md | 3 +++ action.yml | 4 +++ package.json | 1 + src/__tests__/config.test.ts | 2 ++ src/config.ts | 1 + src/libs/__tests__/pr.test.ts | 11 ++++---- src/libs/pr.ts | 50 ++++++++++++++++++++++++++++------- tsconfig.json | 1 + 8 files changed, 59 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c5032eff..2de8afc8 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,9 @@ The action can be configured with the following arguments: `true`. This is in an effort to reduce verbosity - if you want to have a comment per PR run, please ensure that you set this to `false`. +- `comment-max-character` - (optional) Maximum number of characters of the Pulumi + action result to be added to the PR. Default value is 64000. + - `expect-no-changes` - (optional) Return an error if any changes occur during this update. diff --git a/action.yml b/action.yml index ccc5b149..6fad7f39 100644 --- a/action.yml +++ b/action.yml @@ -32,6 +32,10 @@ inputs: description: 'If true, a comment on the GitHub step summary will be created' required: false default: 'false' + comment-max-character: + description: 'Maximum number of characters of output added to the PR.' + required: false + default: "64000" github-token: description: 'Github Token' required: false diff --git a/package.json b/package.json index 3179d71b..8fc78f80 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@actions/tool-cache": "^2.0.1", "@pulumi/pulumi": "3.109.0", "actions-parsers": "^1.0.2", + "ansi-to-html": "^0.7.2", "dedent": "^0.7.0", "envalid": "^7.3.1", "got": "^11.8.6", diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts index a0be6583..a82cc30f 100644 --- a/src/__tests__/config.test.ts +++ b/src/__tests__/config.test.ts @@ -9,6 +9,7 @@ const defaultConfig: Record = { 'pulumi-version': '^3', 'comment-on-pr': 'false', 'comment-on-summary': 'false', + 'comment-max-character': '64000', upsert: 'false', remove: 'false', refresh: 'false', @@ -41,6 +42,7 @@ describe('config.ts', () => { Object { "cloudUrl": "file://~", "command": "up", + "commentMaxCharacter": 64000, "commentOnPr": false, "commentOnPrNumber": undefined, "commentOnSummary": false, diff --git a/src/config.ts b/src/config.ts index 52d203e4..494acc67 100644 --- a/src/config.ts +++ b/src/config.ts @@ -84,6 +84,7 @@ export function makeConfig() { commentOnPr: getBooleanInput('comment-on-pr'), commentOnPrNumber: getNumberInput('comment-on-pr-number', {}), commentOnSummary: getBooleanInput('comment-on-summary'), + commentMaxCharacter: getNumberInput('comment-max-character'), upsert: getBooleanInput('upsert'), remove: getBooleanInput('remove'), refresh: getBooleanInput('refresh'), diff --git a/src/libs/__tests__/pr.test.ts b/src/libs/__tests__/pr.test.ts index 8c189787..febde266 100644 --- a/src/libs/__tests__/pr.test.ts +++ b/src/libs/__tests__/pr.test.ts @@ -1,4 +1,4 @@ -import * as gh from '@actions/github'; +import gh from '@actions/github'; import { Config } from '../../config'; import { handlePullRequestMessage } from '../pr'; @@ -13,6 +13,7 @@ const projectName = 'myFirstProject'; const defaultOptions = { command: 'preview', stackName: 'staging', + commentMaxCharacter: 64000, options: {}, } as Config; const createComment = jest.fn(); @@ -50,7 +51,7 @@ describe('pr.ts', () => { await handlePullRequestMessage(defaultOptions, projectName, 'test'); expect(createComment).toHaveBeenCalledWith({ - body: '#### :tropical_drink: `preview` on myFirstProject/staging\n\n
\nPulumi report\n\n```\ntest\n```\n\n
', + body: '#### :tropical_drink: `preview` on myFirstProject/staging\n\n
\nPulumi report\n\n
\ntest\n
\n\n
', issue_number: 123, }); }); @@ -74,7 +75,7 @@ describe('pr.ts', () => { await handlePullRequestMessage(options, projectName, 'test'); expect(createComment).toHaveBeenCalledWith({ - body: '#### :tropical_drink: `preview` on myFirstProject/staging\n\n
\nPulumi report\n\n```\ntest\n```\n\n
', + body: '#### :tropical_drink: `preview` on myFirstProject/staging\n\n
\nPulumi report\n\n
\ntest\n
\n\n
', issue_number: 87, }); }); @@ -101,7 +102,7 @@ describe('pr.ts', () => { await handlePullRequestMessage(options, projectName, 'test'); expect(createComment).toHaveBeenCalledWith({ - body: '#### :tropical_drink: `preview` on myFirstProject/staging\n\n
\nPulumi report\n\n```\ntest\n```\n\n
', + body: '#### :tropical_drink: `preview` on myFirstProject/staging\n\n
\nPulumi report\n\n
\ntest\n
\n\n
', issue_number: 87, }); }); @@ -142,7 +143,7 @@ describe('pr.ts', () => { await handlePullRequestMessage(options, projectName, 'test'); expect(updateComment).toHaveBeenCalledWith({ comment_id: 2, - body: '#### :tropical_drink: `preview` on myFirstProject/staging\n\n
\nPulumi report\n\n```\ntest\n```\n\n
', + body: '#### :tropical_drink: `preview` on myFirstProject/staging\n\n
\nPulumi report\n\n
\ntest\n
\n\n
', }); }); }); diff --git a/src/libs/pr.ts b/src/libs/pr.ts index e0098a83..01b4c0af 100644 --- a/src/libs/pr.ts +++ b/src/libs/pr.ts @@ -1,9 +1,41 @@ import * as core from '@actions/core'; import { context, getOctokit } from '@actions/github'; -import * as dedent from 'dedent'; +import AnsiToHtml from 'ansi-to-html'; +import dedent from 'dedent'; import invariant from 'ts-invariant'; import { Config } from '../config'; +function ansiToHtml( + message: string, + maxLength: number, +): [string, boolean] { + /** + * Converts an ansi string to html by for example removing color escape characters. + * message: ansi string to convert + * maxLength: Maximum number of characters of final message incl. HTML tags + * + * return message as html and information if message was trimmed because of length + */ + const convert = new AnsiToHtml(); + let trimmed = false; + + let htmlBody: string = convert.toHtml(message); + + // Check if htmlBody exceeds max characters + if (htmlBody.length > maxLength) { + + // trim input message by number of exceeded characters + const dif: number = htmlBody.length - maxLength; + message = message.substring(0, message.length - dif); + trimmed = true; + + // convert trimmed message to html + htmlBody = convert.toHtml(message); + } + + return [htmlBody, trimmed]; +} + export async function handlePullRequestMessage( config: Config, projectName: string, @@ -14,27 +46,27 @@ export async function handlePullRequestMessage( command, stackName, editCommentOnPr, + commentMaxCharacter, } = config; const heading = `#### :tropical_drink: \`${command}\` on ${projectName}/${stackName}`; const summary = 'Pulumi report'; - const rawBody = output.substring(0, 64_000); - // a line break between heading and rawBody is needed - // otherwise the backticks won't work as intended + const [htmlBody, trimmed]: [string, boolean] = ansiToHtml(output, commentMaxCharacter); + const body = dedent` ${heading}
${summary} - \`\`\` - ${rawBody} - \`\`\` +
+    ${htmlBody}
+    
${ - rawBody.length === 64_000 - ? '**Warn**: The output was too long and trimmed.' + trimmed + ? ':warning: **Warn**: The output was too long and trimmed.' : '' }
diff --git a/tsconfig.json b/tsconfig.json index 8ade0419..baa28344 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "target": "es2015", "moduleResolution": "node", "allowSyntheticDefaultImports": true, + "esModuleInterop": true, "allowJs": false, "importHelpers": true, "alwaysStrict": true, From 6ef788f96992a086a0ef1934e2ccb9f71ac138a6 Mon Sep 17 00:00:00 2001 From: jmrt47 Date: Thu, 18 Jul 2024 21:27:52 +0200 Subject: [PATCH 2/6] chore: Add updated yarn.lock file --- yarn.lock | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/yarn.lock b/yarn.lock index c746949f..957761b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1716,6 +1716,13 @@ ansi-styles@^6.1.0, ansi-styles@^6.2.1: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== +ansi-to-html@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.7.2.tgz#a92c149e4184b571eb29a0135ca001a8e2d710cb" + integrity sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g== + dependencies: + entities "^2.2.0" + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -2558,6 +2565,11 @@ enquirer@^2.3.5: dependencies: ansi-colors "^4.1.1" +entities@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + env-paths@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" From 4a6ad7f6d5ca934c8dbca2a698345d830b270ebf Mon Sep 17 00:00:00 2001 From: jmrt47 Date: Thu, 18 Jul 2024 21:34:41 +0200 Subject: [PATCH 3/6] test: Add test case for ansi to html in PR comment --- src/libs/__tests__/pr.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/libs/__tests__/pr.test.ts b/src/libs/__tests__/pr.test.ts index febde266..c51e314c 100644 --- a/src/libs/__tests__/pr.test.ts +++ b/src/libs/__tests__/pr.test.ts @@ -56,6 +56,25 @@ describe('pr.ts', () => { }); }); + it('should convert ansi controll character to html and add to pull request message', async () => { + // @ts-ignore + gh.context = { + payload: { + pull_request: { + number: 123, + }, + }, + }; + + process.env.GITHUB_REPOSITORY = 'pulumi/actions'; + + await handlePullRequestMessage(defaultOptions, projectName, '\x1b[30mblack\x1b[37mwhite'); + expect(createComment).toHaveBeenCalledWith({ + body: '#### :tropical_drink: `preview` on myFirstProject/staging\n\n
\nPulumi report\n\n
\nblackwhite\n
\n\n
', + issue_number: 123, + }); + }); + it('should add pull request message to the PR defined in config, overriding the github context', async () => { // @ts-ignore gh.context = { From 360a0056bc3dcf0d98d97311472c3b53cba8eb55 Mon Sep 17 00:00:00 2001 From: jmrt Date: Thu, 18 Jul 2024 22:03:35 +0200 Subject: [PATCH 4/6] chore: update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c4dd224..e8d27ee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## HEAD (Unreleased) -**(none)** +- feat: strip ansi control characters from pull request comment + ([#1231](https://github.com/pulumi/actions/pull/1231)) --- From 2c920030e0d8f8b51262e429778f938fc5183b6a Mon Sep 17 00:00:00 2001 From: jmrt Date: Wed, 24 Jul 2024 10:40:42 +0200 Subject: [PATCH 5/6] chore: fix typo --- src/libs/__tests__/pr.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/__tests__/pr.test.ts b/src/libs/__tests__/pr.test.ts index c51e314c..0754b1e4 100644 --- a/src/libs/__tests__/pr.test.ts +++ b/src/libs/__tests__/pr.test.ts @@ -56,7 +56,7 @@ describe('pr.ts', () => { }); }); - it('should convert ansi controll character to html and add to pull request message', async () => { + it('should convert ansi control character to html and add to pull request message', async () => { // @ts-ignore gh.context = { payload: { From ec488a048e593d4a41abd67232334e491202d3bf Mon Sep 17 00:00:00 2001 From: jmrt Date: Wed, 24 Jul 2024 13:14:24 +0200 Subject: [PATCH 6/6] chore: remove comment max character config option and add as constant --- README.md | 3 --- action.yml | 4 ---- src/__tests__/config.test.ts | 2 -- src/config.ts | 1 - src/libs/__tests__/pr.test.ts | 1 - src/libs/pr.ts | 6 ++++-- 6 files changed, 4 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2de8afc8..c5032eff 100644 --- a/README.md +++ b/README.md @@ -87,9 +87,6 @@ The action can be configured with the following arguments: `true`. This is in an effort to reduce verbosity - if you want to have a comment per PR run, please ensure that you set this to `false`. -- `comment-max-character` - (optional) Maximum number of characters of the Pulumi - action result to be added to the PR. Default value is 64000. - - `expect-no-changes` - (optional) Return an error if any changes occur during this update. diff --git a/action.yml b/action.yml index 6fad7f39..ccc5b149 100644 --- a/action.yml +++ b/action.yml @@ -32,10 +32,6 @@ inputs: description: 'If true, a comment on the GitHub step summary will be created' required: false default: 'false' - comment-max-character: - description: 'Maximum number of characters of output added to the PR.' - required: false - default: "64000" github-token: description: 'Github Token' required: false diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts index a82cc30f..a0be6583 100644 --- a/src/__tests__/config.test.ts +++ b/src/__tests__/config.test.ts @@ -9,7 +9,6 @@ const defaultConfig: Record = { 'pulumi-version': '^3', 'comment-on-pr': 'false', 'comment-on-summary': 'false', - 'comment-max-character': '64000', upsert: 'false', remove: 'false', refresh: 'false', @@ -42,7 +41,6 @@ describe('config.ts', () => { Object { "cloudUrl": "file://~", "command": "up", - "commentMaxCharacter": 64000, "commentOnPr": false, "commentOnPrNumber": undefined, "commentOnSummary": false, diff --git a/src/config.ts b/src/config.ts index 494acc67..52d203e4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -84,7 +84,6 @@ export function makeConfig() { commentOnPr: getBooleanInput('comment-on-pr'), commentOnPrNumber: getNumberInput('comment-on-pr-number', {}), commentOnSummary: getBooleanInput('comment-on-summary'), - commentMaxCharacter: getNumberInput('comment-max-character'), upsert: getBooleanInput('upsert'), remove: getBooleanInput('remove'), refresh: getBooleanInput('refresh'), diff --git a/src/libs/__tests__/pr.test.ts b/src/libs/__tests__/pr.test.ts index 0754b1e4..0c39d66d 100644 --- a/src/libs/__tests__/pr.test.ts +++ b/src/libs/__tests__/pr.test.ts @@ -13,7 +13,6 @@ const projectName = 'myFirstProject'; const defaultOptions = { command: 'preview', stackName: 'staging', - commentMaxCharacter: 64000, options: {}, } as Config; const createComment = jest.fn(); diff --git a/src/libs/pr.ts b/src/libs/pr.ts index 01b4c0af..8370556c 100644 --- a/src/libs/pr.ts +++ b/src/libs/pr.ts @@ -46,14 +46,16 @@ export async function handlePullRequestMessage( command, stackName, editCommentOnPr, - commentMaxCharacter, } = config; + // GitHub limits comment characters to 65535, use lower max to keep buffer for variable values + const MAX_CHARACTER_COMMENT = 64_000; + const heading = `#### :tropical_drink: \`${command}\` on ${projectName}/${stackName}`; const summary = 'Pulumi report'; - const [htmlBody, trimmed]: [string, boolean] = ansiToHtml(output, commentMaxCharacter); + const [htmlBody, trimmed]: [string, boolean] = ansiToHtml(output, MAX_CHARACTER_COMMENT); const body = dedent` ${heading}