Skip to content

Commit 4d9f208

Browse files
authored
Regenerate Actions utility helpers (#14781)
* Regenerate Actions utility helpers * Fix Actions comment marker pairing
1 parent 0437d24 commit 4d9f208

3 files changed

Lines changed: 194 additions & 9 deletions

File tree

‎.github/actions/common/utils.js‎

Lines changed: 10 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
const assert = require('node:assert/strict');
7+
const { readFileSync } = require('node:fs');
8+
const path = require('node:path');
9+
const test = require('node:test');
10+
const vm = require('node:vm');
11+
const ts = require('typescript');
12+
13+
const configPath = path.join(__dirname, '..', 'tsconfig.json');
14+
const config = ts.readConfigFile(configPath, ts.sys.readFile);
15+
assert.equal(config.error, undefined);
16+
const project = ts.parseJsonConfigFileContent(config.config, ts.sys, path.dirname(configPath));
17+
assert.deepEqual(project.errors, []);
18+
const compiled = ts.transpileModule(readFileSync(path.join(__dirname, 'utils.ts'), 'utf8'), {
19+
fileName: 'utils.ts',
20+
compilerOptions: project.options,
21+
reportDiagnostics: true,
22+
});
23+
assert.deepEqual(compiled.diagnostics, []);
24+
const committed = readFileSync(path.join(__dirname, 'utils.js'), 'utf8');
25+
26+
function printJavaScript(source) {
27+
const tree = ts.createSourceFile('utils.js', source, ts.ScriptTarget.ES2019, true, ts.ScriptKind.JS);
28+
assert.deepEqual(tree.parseDiagnostics, []);
29+
return ts.createPrinter({ removeComments: true }).printFile(tree);
30+
}
31+
32+
test('committed utils.js matches the TypeScript output', () => {
33+
assert.equal(printJavaScript(committed), printJavaScript(compiled.outputText));
34+
});
35+
36+
function loadUtils(source, errorLogIssueNumber = '99') {
37+
const context = {
38+
repo: { owner: 'example', repo: 'triage' },
39+
workflow: 'Helper tests',
40+
issue: { number: 7 },
41+
payload: { action: 'labeled' },
42+
};
43+
const requests = [];
44+
const comments = [];
45+
const delays = [];
46+
const logs = [];
47+
const modules = {
48+
'@actions/core': {
49+
getInput: (name) => name === 'errorLogIssueNumber' ? errorLogIssueNumber : '',
50+
},
51+
'@actions/github': { context },
52+
axios: {},
53+
'../api/octokit': {
54+
OctoKitIssue: class {
55+
constructor(token, repo, issue) {
56+
requests.push(JSON.parse(JSON.stringify({ token, repo, issue })));
57+
}
58+
async postComment(body) {
59+
comments.push(body);
60+
}
61+
},
62+
},
63+
};
64+
const exports = {};
65+
new vm.Script(source, { filename: 'utils.js' }).runInNewContext({
66+
exports,
67+
require: (name) => {
68+
assert.ok(Object.hasOwn(modules, name), `Unexpected dependency: ${name}`);
69+
return modules[name];
70+
},
71+
console: {
72+
log: (...args) => logs.push(args),
73+
error: (error) => assert.fail(`Unexpected error: ${error}`),
74+
},
75+
setTimeout: (callback, delay) => {
76+
delays.push(delay);
77+
callback();
78+
},
79+
}, { timeout: 1000 });
80+
return { utils: exports, context, requests, comments, delays, logs };
81+
}
82+
83+
for (const [name, source] of [
84+
['committed JavaScript', committed],
85+
['TypeScript output', compiled.outputText],
86+
]) {
87+
test(`${name}: normalizes ordinary issue text`, () => {
88+
const { utils } = loadUtils(source);
89+
const result = utils.normalizeIssue({ body: 'Ordinary body', title: 'Useful title' });
90+
assert.equal(result.body, 'ordinary body');
91+
assert.equal(result.title, 'useful title');
92+
assert.equal(result.issueType, 'unknown');
93+
});
94+
95+
test(`${name}: preserves text between separate comments`, () => {
96+
const { utils } = loadUtils(source);
97+
const result = utils.normalizeIssue({
98+
body: 'Start <!-- first note --> keep <!-- second note --> end',
99+
title: 'A <!-- note --> title',
100+
});
101+
assert.equal(result.body, 'start keep end');
102+
assert.equal(result.title, 'a title');
103+
});
104+
105+
test(`${name}: removes ordinary multiline comments`, () => {
106+
const { utils } = loadUtils(source);
107+
const result = utils.normalizeIssue({
108+
body: 'Start <!-- first line\nsecond line --> end',
109+
title: 'Title',
110+
});
111+
assert.equal(result.body, 'start end');
112+
assert.equal(result.title, 'title');
113+
});
114+
115+
test(`${name}: removes comments after unrelated closing markers`, () => {
116+
const { utils } = loadUtils(source);
117+
const result = utils.normalizeIssue({
118+
body: 'Start --> keep <!-- first note --> middle --> text <!-- second note --> end',
119+
title: 'Title --> <!-- note --> text',
120+
});
121+
assert.equal(result.body, 'start --> keep middle --> text end');
122+
assert.equal(result.title, 'title --> text');
123+
});
124+
125+
test(`${name}: preserves incomplete comment text`, () => {
126+
const { utils } = loadUtils(source);
127+
for (const [text, expected] of [
128+
['Start --> end', 'start --> end'],
129+
['Start <!-- note', 'start <!-- note'],
130+
['Start --> text <!-- note', 'start --> text <!-- note'],
131+
['Start <!-- complete --> tail <!-- note', 'start tail <!-- note'],
132+
['Start <!----><!-- note --> end', 'start end'],
133+
]) {
134+
const result = utils.normalizeIssue({ body: text, title: text });
135+
assert.equal(result.body, expected);
136+
assert.equal(result.title, expected);
137+
}
138+
});
139+
140+
test(`${name}: preserves issue classification`, () => {
141+
const { utils } = loadUtils(source);
142+
for (const [body, issueType] of [
143+
['Issue Type: Bug', 'bug'],
144+
['Issue Type: Feature Request', 'feature_request'],
145+
['Ordinary issue', 'unknown'],
146+
]) {
147+
assert.equal(utils.normalizeIssue({ body, title: 'Title' }).issueType, issueType);
148+
}
149+
});
150+
151+
test(`${name}: reports an ordinary error through the stubbed client`, async () => {
152+
for (const ping of [false, true]) {
153+
const runtime = loadUtils(source);
154+
await runtime.utils.logErrorToIssue('Routine test failure', ping, '');
155+
assert.deepEqual(runtime.requests, [{
156+
token: '',
157+
repo: { owner: 'example', repo: 'triage' },
158+
issue: { number: 99 },
159+
}]);
160+
assert.deepEqual(runtime.delays, [10000]);
161+
assert.equal(runtime.comments.length, 1);
162+
const comment = runtime.comments[0];
163+
assert.ok(comment.includes('Workflow: Helper tests'));
164+
assert.ok(comment.includes('Error: Routine test failure'));
165+
assert.ok(comment.includes(`Issue: ${ping ? 'example/triage#' : ''}7`));
166+
assert.ok(comment.includes('Repo: example/triage'));
167+
assert.ok(comment.includes(JSON.stringify(runtime.context, null, 2)));
168+
}
169+
});
170+
171+
test(`${name}: skips reporting when no destination is configured`, async () => {
172+
const runtime = loadUtils(source, '');
173+
await runtime.utils.logErrorToIssue('Routine test failure', false, '');
174+
assert.deepEqual(runtime.requests, []);
175+
assert.deepEqual(runtime.comments, []);
176+
assert.deepEqual(runtime.logs, [['no error logging repo defined. swallowing error.']]);
177+
});
178+
}

‎.github/actions/common/utils.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@ export const normalizeIssue = (issue: {
3535
.replace(/\s+/gu, ' ')
3636
.replace(/```[^`]*?```/gu, '');
3737

38-
while (
39-
out.includes('<!--') &&
40-
out.includes('-->') &&
41-
out.indexOf('-->') > out.indexOf('<!--')) {
42-
out = out.slice(0, out.indexOf('<!--')) + out.slice(out.indexOf('-->') + 3);
38+
for (let start = out.indexOf('<!--'); start !== -1; start = out.indexOf('<!--')) {
39+
const end = out.indexOf('-->', start + 4);
40+
if (end === -1) {
41+
break;
42+
}
43+
out = out.slice(0, start) + out.slice(end + 3);
4344
}
4445
while (
4546
out.includes(`<details>`) &&

0 commit comments

Comments
 (0)