Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

not print untrusted info to STDOUT. #1

Merged
merged 10 commits into from
Nov 22, 2020
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
node_modules/
lib/
__tests__/runner/*
.idea
364 changes: 343 additions & 21 deletions dist/index.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}
};
485 changes: 377 additions & 108 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"pack": "ncc build",
"test": "jest",
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
Expand All @@ -27,18 +28,20 @@
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/github": "^4.0.0",
"@octokit/rest": "^18.0.4",
"@octokit/rest": "^18.0.9",
"lodash.deburr": "^4.1.0",
"semver": "^7.3.2"
},
"devDependencies": {
"@types/semver": "^7.3.1",
"@types/jest": "^26.0.10",
"@types/jest": "^26.0.15",
"@types/lodash.deburr": "^4.1.6",
"@types/node": "^14.10.0",
"@typescript-eslint/parser": "^3.10.1",
"@types/semver": "^7.3.4",
"@typescript-eslint/parser": "^4.8.1",
"@vercel/ncc": "^0.24.0",
"eslint": "^7.7.0",
"eslint-plugin-github": "^4.0.1",
"eslint-plugin-jest": "^23.20.0",
"eslint-plugin-jest": "^24.1.3",
"jest": "^24.9.0",
"jest-circus": "^26.4.2",
"js-yaml": "^3.14.0",
Expand Down
39 changes: 12 additions & 27 deletions src/IssueProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as core from '@actions/core';
import {context, getOctokit} from '@actions/github';
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
import {isLabeled} from './functions/is-labeled';
import {labelsToList} from './functions/labels-to-list';

export interface Issue {
title: string;
Expand Down Expand Up @@ -115,7 +117,7 @@ export class IssueProcessor {
const isPr = !!issue.pull_request;

core.info(
`Found issue: issue #${issue.number} - ${issue.title} last updated ${issue.updated_at} (is pr? ${isPr})`
`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${isPr})`
);

// calculate string based messages for this issue
Expand All @@ -131,7 +133,7 @@ export class IssueProcessor {
const closeLabel: string = isPr
? this.options.closePrLabel
: this.options.closeIssueLabel;
const exemptLabels = IssueProcessor.parseCommaSeparatedString(
const exemptLabels: string[] = labelsToList(
isPr ? this.options.exemptPrLabels : this.options.exemptIssueLabels
);
const skipMessage = isPr
Expand All @@ -157,15 +159,15 @@ export class IssueProcessor {

if (
exemptLabels.some((exemptLabel: string) =>
IssueProcessor.isLabeled(issue, exemptLabel)
isLabeled(issue, exemptLabel)
)
) {
core.info(`Skipping ${issueType} because it has an exempt label`);
continue; // don't process exempt issues
}

// does this issue have a stale label?
let isStale = IssueProcessor.isLabeled(issue, staleLabel);
let isStale = isLabeled(issue, staleLabel);

// should this issue be marked stale?
const shouldBeStale = !IssueProcessor.updatedSince(
Expand Down Expand Up @@ -250,7 +252,7 @@ export class IssueProcessor {
await this.closeIssue(issue, closeMessage, closeLabel);
} else {
core.info(
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate}`
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`
);
}
}
Expand All @@ -277,7 +279,7 @@ export class IssueProcessor {
);

core.info(
`Comments not made by ${context.actor} or another bot: ${filteredComments.length}`
`Comments not made by actor or another bot: ${filteredComments.length}`
);

// if there are any user comments returned
Expand Down Expand Up @@ -336,7 +338,7 @@ export class IssueProcessor {
staleLabel: string,
skipMessage: boolean
): Promise<void> {
core.info(`Marking issue #${issue.number} - ${issue.title} as stale`);
core.info(`Marking issue #${issue.number} as stale`);

this.staleIssues.push(issue);

Expand Down Expand Up @@ -382,9 +384,7 @@ export class IssueProcessor {
closeMessage?: string,
closeLabel?: string
): Promise<void> {
core.info(
`Closing issue #${issue.number} - ${issue.title} for being stale`
);
core.info(`Closing issue #${issue.number} for being stale`);

this.closedIssues.push(issue);

Expand Down Expand Up @@ -434,9 +434,7 @@ export class IssueProcessor {

// Remove a label from an issue
private async removeLabel(issue: Issue, label: string): Promise<void> {
core.info(
`Removing label ${label} from issue #${issue.number} - ${issue.title}`
);
core.info(`Removing label from issue #${issue.number}`);

this.removedLabelIssues.push(issue);

Expand Down Expand Up @@ -464,7 +462,7 @@ export class IssueProcessor {
issue: Issue,
label: string
): Promise<string | undefined> {
core.info(`Checking for label ${label} on issue #${issue.number}`);
core.info(`Checking for label on issue #${issue.number}`);

this.operationsLeft -= 1;

Expand All @@ -490,24 +488,11 @@ export class IssueProcessor {
return staleLabeledEvent.created_at;
}

private static isLabeled(issue: Issue, label: string): boolean {
const labelComparer: (l: Label) => boolean = l =>
label.localeCompare(l.name, undefined, {sensitivity: 'accent'}) === 0;
return issue.labels.filter(labelComparer).length > 0;
}

private static updatedSince(timestamp: string, num_days: number): boolean {
const daysInMillis = 1000 * 60 * 60 * 24 * num_days;
const millisSinceLastUpdated =
new Date().getTime() - new Date(timestamp).getTime();

return millisSinceLastUpdated <= daysInMillis;
}

private static parseCommaSeparatedString(s: string): string[] {
// String.prototype.split defaults to [''] when called on an empty string
// In this case, we'd prefer to just return an empty array indicating no labels
if (!s.length) return [];
return s.split(',').map(l => l.trim());
}
}
187 changes: 187 additions & 0 deletions src/functions/is-labeled.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import {Issue} from '../IssueProcessor';
import {isLabeled} from './is-labeled';

describe('isLabeled()', (): void => {
let issue: Issue;
let label: string;

describe('when the given issue contains no label', (): void => {
beforeEach((): void => {
issue = ({
labels: []
} as unknown) as Issue;
});

describe('when the given label is a simple label', (): void => {
beforeEach((): void => {
label = 'label';
});

it('should return false', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(false);
});
});
});

describe('when the given issue contains a simple label', (): void => {
beforeEach((): void => {
issue = {
labels: [
{
name: 'label'
}
]
} as Issue;
});

describe('when the given label is a simple label', (): void => {
beforeEach((): void => {
label = 'label';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});
});

describe('when the given issue contains a kebab case label', (): void => {
beforeEach((): void => {
issue = {
labels: [
{
name: 'kebab-case-label'
}
]
} as Issue;
});

describe('when the given label is a kebab case label', (): void => {
beforeEach((): void => {
label = 'kebab-case-label';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});
});

describe('when the given issue contains a multiple word label', (): void => {
beforeEach((): void => {
issue = {
labels: [
{
name: 'label like a sentence'
}
]
} as Issue;
});

describe('when the given label is a multiple word label', (): void => {
beforeEach((): void => {
label = 'label like a sentence';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});
});

describe('when the given issue contains a multiple word label with %20 spaces', (): void => {
beforeEach((): void => {
issue = {
labels: [
{
name: 'label%20like%20a%20sentence'
}
]
} as Issue;
});

describe('when the given label is a multiple word label with %20 spaces', (): void => {
beforeEach((): void => {
label = 'label%20like%20a%20sentence';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});
});

describe('when the given issue contains a label wih diacritical marks', (): void => {
beforeEach((): void => {
issue = {
labels: [
{
name: 'déjà vu'
}
]
} as Issue;
});

describe('when the given issue contains a label', (): void => {
beforeEach((): void => {
label = 'deja vu';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});

describe('when the given issue contains an uppercase label', (): void => {
beforeEach((): void => {
label = 'DEJA VU';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});

describe('when the given issue contains a label wih diacritical marks', (): void => {
beforeEach((): void => {
label = 'déjà vu';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});
});
});
24 changes: 24 additions & 0 deletions src/functions/is-labeled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import deburr from 'lodash.deburr';
import {Issue, Label} from '../IssueProcessor';

/**
* @description
* Check if the label is listed as a label of the issue
*
* @param {Readonly<Issue>} issue A GitHub issue containing some labels
* @param {Readonly<string>} label The label to check the presence with
*
* @return {boolean} Return true when the given label is also in the issue labels
*/
export function isLabeled(
issue: Readonly<Issue>,
label: Readonly<string>
): boolean {
return !!issue.labels.find((issueLabel: Readonly<Label>): boolean => {
return cleanLabel(label) === cleanLabel(issueLabel.name);
});
}

function cleanLabel(label: Readonly<string>): string {
return deburr(label.toLowerCase());
}
Loading