Commit 58a6f2f2 authored by Patrick Rice's avatar Patrick Rice ☀️ Committed by Timo Furrer
Browse files

ci(no-release): update commitlint to use scripted approach

Changelog: Improvements
parent 7060b47e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ _testmain.go
vendor
.go/
.golangci-lint/
scripts/commitlint/node_modules

# reports
gl-code-quality-report.json
+8 −5
Original line number Diff line number Diff line
@@ -138,12 +138,15 @@ commitlint:
  stage: lint
  needs: []
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
  image:
    name: commitlint/commitlint:19.9.1
    entrypoint: [""]
    # lint.js script makes an API call without authentication, so the project or fork must be public
    - if: '$CI_MERGE_REQUEST_IID && $CI_PROJECT_VISIBILITY == "public"' 
      when: always
  image: ${GITLAB_DEPENDENCY_PROXY}node:22-slim
  script:
    - commitlint --from ${CI_MERGE_REQUEST_DIFF_BASE_SHA} --to ${CI_COMMIT_SHA}
    - apt-get update && apt-get install -y git
    - git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME $CI_COMMIT_SHA
    - cd scripts/commitlint && npm ci
    - ./lint.sh

tests:unit:
  extends:
+1 −0
Original line number Diff line number Diff line
@@ -2,3 +2,4 @@ golang 1.23
golangci-lint 2.3.1
gofumpt       0.8.0
buf           1.55.1
node          24.8.0
 No newline at end of file

commitlint.config.mjs

deleted100644 → 0
+0 −8
Original line number Diff line number Diff line
export default {
  extends: ['@commitlint/config-conventional'],
  "rules": {
    "body-max-line-length": [0, "always", 100],
    "subject-case": [0, "always", ["sentence-case"]],
    "header-max-length": [2, "always", 200]
  }
};
+103 −0
Original line number Diff line number Diff line
import read from '@commitlint/read';
import lint from '@commitlint/lint';
import format from '@commitlint/format';
import config from '@commitlint/config-conventional';

// You can test the script by setting these environment variables
const {
  CI_MERGE_REQUEST_DIFF_BASE_SHA, // refers to the main branch
  CI_MERGE_REQUEST_SQUASH_ON_MERGE, // true if the squash MR checkbox is ticked
  CI_MERGE_REQUEST_TITLE, // MR Title
  CI_MERGE_REQUEST_EVENT_TYPE, // equal to 'merge_train' if the pipeline is a merge train pipeline
  CI, // true when script is run in a CI/CD pipeline
  LAST_MR_COMMIT, // This variable is created by `lint.sh` script. It represents the MR commit that's direct parent of the newly created merge commit.
} = process.env;

// Leaving this alone for now even though it doesn't point to client-go because 
// we'll want to create our own commit documentation eventually. I don't think we need to
// for the first iteration though.
const urlSemanticRelease =
  'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/blob/main/docs/developer/commits.md';

// See rule docs https://commitlint.js.org/#/reference-rules
const customRules = {
    "body-max-line-length": [0, "always", 100],
    "subject-case": [0, "always", ["sentence-case"]],
    "header-max-length": [2, "always", 200],

    // Inherited from upstream cli configuration, but 
    // I think they're worth leaving for client-go.
    "body-leading-blank": [2, "always"],
    "footer-leading-blank": [2, "always"]
};

async function getCommitsInMr() {
  const diffBaseSha = CI_MERGE_REQUEST_DIFF_BASE_SHA;
  const sourceBranchSha = LAST_MR_COMMIT;
  const messages = await read({ from: diffBaseSha, to: sourceBranchSha });
  return messages;
}

const messageMatcher = r => r.test.bind(r);

async function isConventional(message) {
  return lint(
    message,
    { ...config.rules, ...customRules },
    {
      defaultIgnores: false,
      ignores: [
        messageMatcher(/^[Rr]evert .*/),
        messageMatcher(/^(?:fixup|squash)!/),
        messageMatcher(/^Merge branch/),
        messageMatcher(/^\d+\.\d+\.\d+/),
      ],
    },
  );
}

async function lintMr() {
  const commits = await getCommitsInMr();

  // When MR is set to squash, but it's not yet being merged, we check the MR Title
  if (
    CI_MERGE_REQUEST_SQUASH_ON_MERGE === 'true' &&
    CI_MERGE_REQUEST_EVENT_TYPE !== 'merge_train'
  ) {
    console.log(
      'INFO: The MR is set to squash. We will lint the MR Title (used as the commit message by default).',
    );
    return isConventional(CI_MERGE_REQUEST_TITLE).then(Array.of);
  }
  console.log('INFO: Checking all commits that will be added by this MR.');
  return Promise.all(commits.map(commit => isConventional(commit)));
}

async function run() {
  if (!CI) {
    console.error('This script can only run in GitLab CI.');
    process.exit(1);
  }

  if (!LAST_MR_COMMIT) {
    console.error(
      'LAST_MR_COMMIT environment variable is not present. Make sure this script is run from `lint.sh`',
    );
    process.exit(1);
  }

  const results = await lintMr();

  console.error(format({ results }, { helpUrl: urlSemanticRelease }));

  const numOfErrors = results.reduce((acc, result) => acc + result.errors.length, 0);
  if (numOfErrors !== 0) {
    process.exit(1);
  }
}

run().catch(err => {
  console.error(err);
  process.exit(1);
});
Loading