Add update-only configuration option (#92)

This commit is contained in:
Alex Hatzenbuhler 2023-05-02 17:11:22 -05:00 committed by GitHub
parent 387ece43e3
commit 1dff58b1a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 3311 additions and 10787 deletions

1
.gitignore vendored
View file

@ -7,6 +7,7 @@ node_modules/
# Editors # Editors
.vscode .vscode
.idea/**
# Logs # Logs
logs logs

View file

@ -82,6 +82,7 @@ jobs:
| allow-repeats | with | Boolean flag to allow identical messages to be posted each time this action is run. | no | false | | allow-repeats | with | Boolean flag to allow identical messages to be posted each time this action is run. | no | false |
| proxy-url | with | String for your proxy service URL if you'd like this to work with fork-based PRs. | no | | | proxy-url | with | String for your proxy service URL if you'd like this to work with fork-based PRs. | no | |
| issue | with | Optional issue number override. | no | | | issue | with | Optional issue number override. | no | |
| update-only | with | Only update the comment if it already exists. | no | |
| GITHUB_TOKEN | env | Valid GitHub token, can alternatively be defined in the env. | no | | | GITHUB_TOKEN | env | Valid GitHub token, can alternatively be defined in the env. | no | |
## Advanced Uses ## Advanced Uses

View file

@ -24,6 +24,7 @@ type Inputs = {
'message-failure'?: string 'message-failure'?: string
'message-cancelled'?: string 'message-cancelled'?: string
'message-skipped'?: string 'message-skipped'?: string
'update-only'?: string
status?: 'success' | 'failure' | 'cancelled' | 'skipped' status?: 'success' | 'failure' | 'cancelled' | 'skipped'
} }
@ -175,6 +176,15 @@ describe('add-pr-comment action', () => {
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true') expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'true')
}) })
it('does not create a comment when updateOnly is true and no existing comment is found', async () => {
inputs.message = simpleMessage
inputs['allow-repeats'] = 'true'
inputs['update-only'] = 'true'
await expect(run()).resolves.not.toThrow()
expect(core.setOutput).toHaveBeenCalledWith('comment-created', 'false')
})
it('creates a comment in another repo', async () => { it('creates a comment in another repo', async () => {
inputs.message = simpleMessage inputs.message = simpleMessage
inputs['repo-owner'] = 'my-owner' inputs['repo-owner'] = 'my-owner'

View file

@ -53,6 +53,9 @@ inputs:
issue: issue:
description: "Override the message when a run is cancelled." description: "Override the message when a run is cancelled."
required: false required: false
update-only:
description: "Only update the comment if it already exists."
required: false
outputs: outputs:
comment-created: comment-created:
description: "Whether a comment was created." description: "Whether a comment was created."

130
dist/index.js vendored
View file

@ -112,6 +112,7 @@ async function getInputs() {
const proxyUrl = core.getInput('proxy-url', { required: false }).replace(/\/$/, ''); const proxyUrl = core.getInput('proxy-url', { required: false }).replace(/\/$/, '');
const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true'; const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true';
const refreshMessagePosition = core.getInput('refresh-message-position', { required: false }) === 'true'; const refreshMessagePosition = core.getInput('refresh-message-position', { required: false }) === 'true';
const updateOnly = core.getInput('update-only', { required: false }) === 'true';
if (messageInput && messagePath) { if (messageInput && messagePath) {
throw new Error('must specify only one, message or message-path'); throw new Error('must specify only one, message or message-path');
} }
@ -125,6 +126,7 @@ async function getInputs() {
const messageSuccess = core.getInput(`message-success`); const messageSuccess = core.getInput(`message-success`);
const messageFailure = core.getInput(`message-failure`); const messageFailure = core.getInput(`message-failure`);
const messageCancelled = core.getInput(`message-cancelled`); const messageCancelled = core.getInput(`message-cancelled`);
const messageSkipped = core.getInput(`message-skipped`);
if (status === 'success' && messageSuccess) { if (status === 'success' && messageSuccess) {
message = messageSuccess; message = messageSuccess;
} }
@ -134,11 +136,13 @@ async function getInputs() {
if (status === 'cancelled' && messageCancelled) { if (status === 'cancelled' && messageCancelled) {
message = messageCancelled; message = messageCancelled;
} }
if (status === 'skipped' && messageSkipped) {
message = messageSkipped;
}
if (!message) { if (!message) {
throw new Error('no message, check your message inputs'); throw new Error('no message, check your message inputs');
} }
const { payload } = github.context; const { payload } = github.context;
return { return {
refreshMessagePosition, refreshMessagePosition,
allowRepeats, allowRepeats,
@ -152,6 +156,7 @@ async function getInputs() {
commitSha: github.context.sha, commitSha: github.context.sha,
owner: repoOwner || payload.repo.owner, owner: repoOwner || payload.repo.owner,
repo: repoName || payload.repo.repo, repo: repoName || payload.repo.repo,
updateOnly: updateOnly,
}; };
} }
exports.getInputs = getInputs; exports.getInputs = getInputs;
@ -217,7 +222,7 @@ const issues_1 = __nccwpck_require__(6962);
const proxy_1 = __nccwpck_require__(8689); const proxy_1 = __nccwpck_require__(8689);
const run = async () => { const run = async () => {
try { try {
const { allowRepeats, message, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, } = await (0, config_1.getInputs)(); const { allowRepeats, message, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, updateOnly, } = await (0, config_1.getInputs)();
const octokit = github.getOctokit(repoToken); const octokit = github.getOctokit(repoToken);
let issueNumber; let issueNumber;
if (issue) { if (issue) {
@ -243,6 +248,12 @@ const run = async () => {
core.debug(`existing comment found with id: ${existingCommentId}`); core.debug(`existing comment found with id: ${existingCommentId}`);
} }
} }
// if no existing comment and updateOnly is true, exit
if (!existingCommentId && updateOnly) {
core.info('no existing comment found and update-only is true, exiting');
core.setOutput('comment-created', 'false');
return;
}
let comment; let comment;
const body = `${messageId}\n\n${message}`; const body = `${messageId}\n\n${message}`;
if (proxyUrl) { if (proxyUrl) {
@ -280,6 +291,10 @@ const run = async () => {
} }
} }
catch (err) { catch (err) {
if (process.env.NODE_ENV === 'test') {
// eslint-disable-next-line no-console
console.log(err);
}
if (err instanceof Error) { if (err instanceof Error) {
core.setFailed(err.message); core.setFailed(err.message);
} }
@ -2256,6 +2271,10 @@ function checkBypass(reqUrl) {
if (!reqUrl.hostname) { if (!reqUrl.hostname) {
return false; return false;
} }
const reqHost = reqUrl.hostname;
if (isLoopbackAddress(reqHost)) {
return true;
}
const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''; const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
if (!noProxy) { if (!noProxy) {
return false; return false;
@ -2281,13 +2300,24 @@ function checkBypass(reqUrl) {
.split(',') .split(',')
.map(x => x.trim().toUpperCase()) .map(x => x.trim().toUpperCase())
.filter(x => x)) { .filter(x => x)) {
if (upperReqHosts.some(x => x === upperNoProxyItem)) { if (upperNoProxyItem === '*' ||
upperReqHosts.some(x => x === upperNoProxyItem ||
x.endsWith(`.${upperNoProxyItem}`) ||
(upperNoProxyItem.startsWith('.') &&
x.endsWith(`${upperNoProxyItem}`)))) {
return true; return true;
} }
} }
return false; return false;
} }
exports.checkBypass = checkBypass; exports.checkBypass = checkBypass;
function isLoopbackAddress(host) {
const hostLower = host.toLowerCase();
return (hostLower === 'localhost' ||
hostLower.startsWith('127.') ||
hostLower.startsWith('[::1]') ||
hostLower.startsWith('[0:0:0:0:0:0:0:1]'));
}
//# sourceMappingURL=proxy.js.map //# sourceMappingURL=proxy.js.map
/***/ }), /***/ }),
@ -6336,6 +6366,20 @@ const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original)
return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest); return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest);
}; };
/**
* isSameProtocol reports whether the two provided URLs use the same protocol.
*
* Both domains must already be in canonical form.
* @param {string|URL} original
* @param {string|URL} destination
*/
const isSameProtocol = function isSameProtocol(destination, original) {
const orig = new URL$1(original).protocol;
const dest = new URL$1(destination).protocol;
return orig === dest;
};
/** /**
* Fetch function * Fetch function
* *
@ -6367,7 +6411,7 @@ function fetch(url, opts) {
let error = new AbortError('The user aborted a request.'); let error = new AbortError('The user aborted a request.');
reject(error); reject(error);
if (request.body && request.body instanceof Stream.Readable) { if (request.body && request.body instanceof Stream.Readable) {
request.body.destroy(error); destroyStream(request.body, error);
} }
if (!response || !response.body) return; if (!response || !response.body) return;
response.body.emit('error', error); response.body.emit('error', error);
@ -6408,9 +6452,43 @@ function fetch(url, opts) {
req.on('error', function (err) { req.on('error', function (err) {
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
if (response && response.body) {
destroyStream(response.body, err);
}
finalize(); finalize();
}); });
fixResponseChunkedTransferBadEnding(req, function (err) {
if (signal && signal.aborted) {
return;
}
if (response && response.body) {
destroyStream(response.body, err);
}
});
/* c8 ignore next 18 */
if (parseInt(process.version.substring(1)) < 14) {
// Before Node.js 14, pipeline() does not fully support async iterators and does not always
// properly handle when the socket close/end events are out of order.
req.on('socket', function (s) {
s.addListener('close', function (hadError) {
// if a data listener is still present we didn't end cleanly
const hasDataListener = s.listenerCount('data') > 0;
// if end happened before close but the socket didn't emit an error, do it now
if (response && hasDataListener && !hadError && !(signal && signal.aborted)) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
response.body.emit('error', err);
}
});
});
}
req.on('response', function (res) { req.on('response', function (res) {
clearTimeout(reqTimeout); clearTimeout(reqTimeout);
@ -6482,7 +6560,7 @@ function fetch(url, opts) {
size: request.size size: request.size
}; };
if (!isDomainOrSubdomain(request.url, locationURL)) { if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) { for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) {
requestOpts.headers.delete(name); requestOpts.headers.delete(name);
} }
@ -6575,6 +6653,13 @@ function fetch(url, opts) {
response = new Response(body, response_options); response = new Response(body, response_options);
resolve(response); resolve(response);
}); });
raw.on('end', function () {
// some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted.
if (!response) {
response = new Response(body, response_options);
resolve(response);
}
});
return; return;
} }
@ -6594,6 +6679,41 @@ function fetch(url, opts) {
writeToStream(req, request); writeToStream(req, request);
}); });
} }
function fixResponseChunkedTransferBadEnding(request, errorCallback) {
let socket;
request.on('socket', function (s) {
socket = s;
});
request.on('response', function (response) {
const headers = response.headers;
if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) {
response.once('close', function (hadError) {
// if a data listener is still present we didn't end cleanly
const hasDataListener = socket.listenerCount('data') > 0;
if (hasDataListener && !hadError) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
errorCallback(err);
}
});
}
});
}
function destroyStream(stream, err) {
if (stream.destroy) {
stream.destroy(err);
} else {
// node < 8
stream.emit('error', err);
stream.end();
}
}
/** /**
* Redirect code matching * Redirect code matching
* *

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View file

@ -44,6 +44,7 @@ async function getInputs() {
const proxyUrl = core.getInput('proxy-url', { required: false }).replace(/\/$/, ''); const proxyUrl = core.getInput('proxy-url', { required: false }).replace(/\/$/, '');
const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true'; const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true';
const refreshMessagePosition = core.getInput('refresh-message-position', { required: false }) === 'true'; const refreshMessagePosition = core.getInput('refresh-message-position', { required: false }) === 'true';
const updateOnly = core.getInput('update-only', { required: false }) === 'true';
if (messageInput && messagePath) { if (messageInput && messagePath) {
throw new Error('must specify only one, message or message-path'); throw new Error('must specify only one, message or message-path');
} }
@ -87,6 +88,7 @@ async function getInputs() {
commitSha: github.context.sha, commitSha: github.context.sha,
owner: repoOwner || payload.repo.owner, owner: repoOwner || payload.repo.owner,
repo: repoName || payload.repo.repo, repo: repoName || payload.repo.repo,
updateOnly: updateOnly,
}; };
} }
exports.getInputs = getInputs; exports.getInputs = getInputs;

View file

@ -31,7 +31,7 @@ const issues_1 = require("./issues");
const proxy_1 = require("./proxy"); const proxy_1 = require("./proxy");
const run = async () => { const run = async () => {
try { try {
const { allowRepeats, message, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, } = await (0, config_1.getInputs)(); const { allowRepeats, message, messageId, refreshMessagePosition, repoToken, proxyUrl, issue, pullRequestNumber, commitSha, repo, owner, updateOnly, } = await (0, config_1.getInputs)();
const octokit = github.getOctokit(repoToken); const octokit = github.getOctokit(repoToken);
let issueNumber; let issueNumber;
if (issue) { if (issue) {
@ -57,6 +57,12 @@ const run = async () => {
core.debug(`existing comment found with id: ${existingCommentId}`); core.debug(`existing comment found with id: ${existingCommentId}`);
} }
} }
// if no existing comment and updateOnly is true, exit
if (!existingCommentId && updateOnly) {
core.info('no existing comment found and update-only is true, exiting');
core.setOutput('comment-created', 'false');
return;
}
let comment; let comment;
const body = `${messageId}\n\n${message}`; const body = `${messageId}\n\n${message}`;
if (proxyUrl) { if (proxyUrl) {
@ -94,6 +100,10 @@ const run = async () => {
} }
} }
catch (err) { catch (err) {
if (process.env.NODE_ENV === 'test') {
// eslint-disable-next-line no-console
console.log(err);
}
if (err instanceof Error) { if (err instanceof Error) {
core.setFailed(err.message); core.setFailed(err.message);
} }

11330
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,7 @@ interface Inputs {
pullRequestNumber?: number pullRequestNumber?: number
repo: string repo: string
owner: string owner: string
updateOnly: boolean
} }
export async function getInputs(): Promise<Inputs> { export async function getInputs(): Promise<Inputs> {
@ -35,6 +36,7 @@ export async function getInputs(): Promise<Inputs> {
const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true' const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true'
const refreshMessagePosition = const refreshMessagePosition =
core.getInput('refresh-message-position', { required: false }) === 'true' core.getInput('refresh-message-position', { required: false }) === 'true'
const updateOnly = core.getInput('update-only', { required: false }) === 'true'
if (messageInput && messagePath) { if (messageInput && messagePath) {
throw new Error('must specify only one, message or message-path') throw new Error('must specify only one, message or message-path')
@ -88,5 +90,6 @@ export async function getInputs(): Promise<Inputs> {
commitSha: github.context.sha, commitSha: github.context.sha,
owner: repoOwner || payload.repo.owner, owner: repoOwner || payload.repo.owner,
repo: repoName || payload.repo.repo, repo: repoName || payload.repo.repo,
updateOnly: updateOnly,
} }
} }

View file

@ -25,6 +25,7 @@ const run = async (): Promise<void> => {
commitSha, commitSha,
repo, repo,
owner, owner,
updateOnly,
} = await getInputs() } = await getInputs()
const octokit = github.getOctokit(repoToken) const octokit = github.getOctokit(repoToken)
@ -60,6 +61,13 @@ const run = async (): Promise<void> => {
} }
} }
// if no existing comment and updateOnly is true, exit
if (!existingCommentId && updateOnly) {
core.info('no existing comment found and update-only is true, exiting')
core.setOutput('comment-created', 'false')
return
}
let comment: CreateIssueCommentResponseData | null | undefined let comment: CreateIssueCommentResponseData | null | undefined
const body = `${messageId}\n\n${message}` const body = `${messageId}\n\n${message}`