diff --git a/.github/workflows/test-workflow.yml b/.github/workflows/test-workflow.yml index bbcc469..ff74eaa 100644 --- a/.github/workflows/test-workflow.yml +++ b/.github/workflows/test-workflow.yml @@ -17,13 +17,7 @@ jobs: name: Validate PR title runs-on: ubuntu-latest timeout-minutes: 5 - permissions: - checks: write - pull-request: read - contents: read steps: - # Checkout only needed for testing in own repo - # Omit checkout when adding to separate repo - name: Clone repository to runner uses: actions/checkout@v3 diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..83ef56d --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,11 @@ +module.exports = { + semi: true, + trailingComma: "all", + bracketSpacing: true, + useTabs: false, + tabWidth: 2, + arrowParens: "always", + singleQuote: false, + quoteProps: "as-needed", + endOfLine: "lf", +}; diff --git a/index.js b/index.js index 91485a4..200b8d5 100644 --- a/index.js +++ b/index.js @@ -9,7 +9,7 @@ async function run() { if (!contextPullRequest) { throw new Error( - "This action may only be triggered by `pull_request` events. Set `pull_request` in the `on` section in your workflow." + "This action may only be triggered by `pull_request` events. Set `pull_request` in the `on` section in your workflow.", ); } @@ -34,7 +34,7 @@ async function run() { console.error("PR title failed validation"); core.setOutput( "validation_issues", - issues.map((issue) => `- ${issue}`).join("\n") + issues.map((issue) => `- ${issue}`).join("\n"), ); throw new Error(issues); diff --git a/src/constants.js b/src/constants.js index 83a4776..dbf2a51 100644 --- a/src/constants.js +++ b/src/constants.js @@ -27,7 +27,7 @@ const ERRORS = { TYPE_NOT_FOUND: `Failed to find \`type\` in PR title. Expected one of ${displayTypes}`, INVALID_TYPE: `Unknown \`type\` in PR title. Expected one of ${displayTypes}`, INVALID_SCOPE: `Unknown \`scope\` in PR title. Expected one of ${displayScopes} or \` Node\``, - UPPERCASE_INITIAL_IN_SUBJECT: "First char of subject must be lowercase", + LOWERCASE_INITIAL_IN_SUBJECT: "First char of subject must be uppercase", FINAL_PERIOD_IN_SUBJECT: "Subject must not end with a period", NO_PRESENT_TENSE_IN_SUBJECT: "Subject must use present tense", SKIP_CHANGELOG_NOT_IN_FINAL_POSITION: `\`${NO_CHANGELOG}\` must be located at the end of the subject`, diff --git a/src/getAllNodesDisplayNames.js b/src/getAllNodesDisplayNames.js index ace571c..b43f84d 100644 --- a/src/getAllNodesDisplayNames.js +++ b/src/getAllNodesDisplayNames.js @@ -9,7 +9,7 @@ const { PARSER_CONTENT } = require("./parserContent"); async function getAllNodesDisplayNames() { try { await exec( - `npm i typescript fast-glob; npm i -g ts-node; touch parser.ts; echo "${PARSER_CONTENT}" > parser.ts` + `npm i typescript fast-glob; npm i -g ts-node; touch parser.ts; echo "${PARSER_CONTENT}" > parser.ts`, ); const result = await exec("ts-node parser.ts"); diff --git a/src/validatePrTitle.js b/src/validatePrTitle.js index 86be7c1..0e7bf7c 100644 --- a/src/validatePrTitle.js +++ b/src/validatePrTitle.js @@ -3,7 +3,7 @@ const { getAllNodesDisplayNames } = require("./getAllNodesDisplayNames"); const { TYPES, SCOPES, NO_CHANGELOG, ERRORS, REGEXES } = require("./constants"); /** - * Validate that a pull request title match n8n's version of the Conventional Commits spec. + * Validate that a pull request title matches n8n's version of the Conventional Commits spec. * * See: https://www.notion.so/n8n/Release-Process-fce65faea3d5403a85210f7e7a60d0f8 */ @@ -39,7 +39,7 @@ async function validatePrTitle(title) { issues.push(ERRORS.MISSING_WHITESPACE_AFTER_COMMA); } else { const scopeIssues = await Promise.all( - scope.split(", ").map(getScopeIssue) + scope.split(", ").map(getScopeIssue), ); issues.push(...scopeIssues.filter((scopeIssue) => scopeIssue !== null)); } @@ -49,8 +49,8 @@ async function validatePrTitle(title) { const { subject } = match.groups; - if (startsWithUpperCase(subject)) { - issues.push(ERRORS.UPPERCASE_INITIAL_IN_SUBJECT); + if (startsWithLowerCase(subject)) { + issues.push(ERRORS.LOWERCASE_INITIAL_IN_SUBJECT); } if (endsWithPeriod(subject)) { @@ -99,7 +99,7 @@ const getScopeIssue = async (scope) => { return null; }; -const startsWithUpperCase = (str) => /^[A-Z]/.test(str); +const startsWithLowerCase = (str) => /^[a-z]/.test(str); const endsWithPeriod = (str) => /\.$/.test(str); diff --git a/src/validatePrTitle.test.js b/src/validatePrTitle.test.js index b2da38e..7c77212 100644 --- a/src/validatePrTitle.test.js +++ b/src/validatePrTitle.test.js @@ -14,21 +14,21 @@ jest describe("schema", () => { test("Validation should fail for conventional schema mismatch", () => { - ["feat(core):", "feat(core)", "feat(core):implement feature"].forEach( + ["feat(core):", "feat(core)", "feat(core): Implement feature"].forEach( async (title) => { const issues = await validate(title); expect(issues) .toHaveLength(1) .toContain(ERRORS.CONVENTIONAL_SCHEMA_MISMATCH); - } + }, ); }); test("Validation should fail for valid ticket number in schema", () => { [ - "feat(Mattermost node): add new resource n8n-1234", - "n8n-1234 feat(Mattermost node): add new resource", - "feat(Mattermost node) n8n-1234: add new resource", + "feat(Mattermost node): Add new resource n8n-1234", + "n8n-1234 feat(Mattermost node): Add new resource", + "feat(Mattermost node) n8n-1234: Add new resource", ].forEach(async (title) => { const issues = await validate(title); expect(issues).toHaveLength(1).toContain(ERRORS.TICKET_NUMBER_PRESENT); @@ -39,61 +39,61 @@ describe("schema", () => { describe("type", () => { test("Validation should pass for valid type", () => { TYPES.forEach(async (type) => { - const issues = await validate(`${type}(core): implement feature`); + const issues = await validate(`${type}(core): Implement feature`); expect(issues).toHaveLength(0); }); }); test("Validation should fail for invalid type", async () => { - const issues = await validate("wrong(core): implement feature"); + const issues = await validate("wrong(core): Implement feature"); expect(issues).toHaveLength(1).toContain(ERRORS.INVALID_TYPE); }); }); describe("scope", () => { test("Validation should pass for valid title with scope", async () => { - const issues = await validate("feat(core): implement feature"); + const issues = await validate("feat(core): Implement feature"); expect(issues).toHaveLength(0); }); test("Validation should pass for title with multiple valid scopes", async () => { - const issues = await validate("feat(core, editor): implement feature"); + const issues = await validate("feat(core, editor): Implement feature"); expect(issues).toHaveLength(0); }); test("Validation should fail for title with valid scope and invalid scope", async () => { - const issues = await validate("feat(core, wrong): implement feature"); + const issues = await validate("feat(core, wrong): Implement feature"); expect(issues).toHaveLength(1).toContain(ERRORS.INVALID_SCOPE); }); test("Validation should fail for title with multiple invalid scopes", async () => { - const issues = await validate("feat(wrong1, wrong2): implement feature"); + const issues = await validate("feat(wrong1, wrong2): Implement feature"); expect(issues).toHaveLength(2).toContain(ERRORS.INVALID_SCOPE); }); test("Validation should fail for title with misdelimited valid scopes", async () => { - expect(await validate("feat(core,editor): implement feature")) + expect(await validate("feat(core,editor): Implement feature")) .toHaveLength(1) .toContain(ERRORS.MISSING_WHITESPACE_AFTER_COMMA); }); test("Validation should pass for valid title without scope", async () => { - const issues = await validate("feat: implement feature"); + const issues = await validate("feat: Implement feature"); expect(issues).toHaveLength(0); }); test("Validation should fail for miscased node suffix", async () => { - const issues = await validate("feat(Mattermost node): add new resource"); + const issues = await validate("feat(Mattermost node): Add new resource"); expect(issues).toHaveLength(1).toContain(ERRORS.INVALID_SCOPE); }); test("Validation should fail for misspelled node scope", async () => { - let issues = await validate("feat(Mattermos Node): change default value"); + let issues = await validate("feat(Mattermos Node): Change default value"); expect(issues) .toHaveLength(1) .toContain(ERRORS.INVALID_SCOPE + ". Did you mean `Mattermost Node`?"); - issues = await validate("feat(Gmai Trigger Node): change default value"); + issues = await validate("feat(Gmai Trigger Node): Change default value"); expect(issues) .toHaveLength(1) .toContain(ERRORS.INVALID_SCOPE + ". Did you mean `Gmail Trigger Node`?"); @@ -101,27 +101,27 @@ describe("scope", () => { }); describe("subject", () => { - test("Validation should fail for uppercase initial in subject", async () => { - const issues = await validate("feat(core): Implement feature"); + test("Validation should fail for lowercase initial in subject", async () => { + const issues = await validate("feat(core): implement feature"); expect(issues) .toHaveLength(1) - .toContain(ERRORS.UPPERCASE_INITIAL_IN_SUBJECT); + .toContain(ERRORS.LOWERCASE_INITIAL_IN_SUBJECT); }); test("Validation should fail for final period in subject", async () => { - const issues = await validate("feat(core): implement feature."); + const issues = await validate("feat(core): Implement feature."); expect(issues).toHaveLength(1).toContain(ERRORS.FINAL_PERIOD_IN_SUBJECT); }); test("Validation should pass for present-tense verb", async () => { - const issues = await validate("feat(editor): update something"); + const issues = await validate("feat(editor): Update something"); expect(issues).toHaveLength(0); }); test("Validation should fail for non-present-tense verb", async () => { [ - "feat(Mattermost Node): added new resource", - "feat(Mattermost Node): created new resource", + "feat(Mattermost Node): Added new resource", + "feat(Mattermost Node): Created new resource", ].forEach(async (title) => { const issues = await validate(title); expect(issues) @@ -131,20 +131,20 @@ describe("subject", () => { }); test("Validation should pass for breaking-change indicator `!`", async () => { - const issues = await validate("feat(Oura Node)!: change default value"); + const issues = await validate("feat(Oura Node)!: Change default value"); expect(issues).toHaveLength(0); }); test(`Validation should pass for \`${NO_CHANGELOG}\` in final position`, async () => { - const issues = await validate("docs(Oura Node): fix typo (no-changelog)"); + const issues = await validate("docs(Oura Node): Fix typo (no-changelog)"); expect(issues).toHaveLength(0); }); test(`Validation should fail for \`${NO_CHANGELOG}\` not in final position`, () => { [ - "docs(Oura Node): fix (no-changelog) typo", - "docs(Oura Node): fix typo(no-changelog) ", - "docs(Oura Node): (no-changelog) fix typo", + "docs(Oura Node): Fix (no-changelog) typo", + "docs(Oura Node): Fix typo(no-changelog) ", + "docs(Oura Node): (no-changelog) Fix typo", ].forEach(async (title) => { const issues = await validate(title); expect(issues)