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

#3: Add CI workflow #7

Merged
merged 30 commits into from
Mar 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
562c661
chore(ci): Set up CI pipeline.
joberstein Feb 16, 2023
c1e7616
chore(ci): Set 'Validate Commits' repo to the root directory.
joberstein Feb 16, 2023
e453463
chore(ci): Fix 'Validate Commits' path.
joberstein Feb 16, 2023
16a7048
chore(ci): Build application before running 'Validate Commits' action.
joberstein Feb 16, 2023
c4241ae
chore(ci): Use npm cache.
joberstein Feb 16, 2023
f19b8f6
chore(ci): Update tests with git user and update action pre file.
joberstein Feb 16, 2023
c06eb40
chore(tests): Set default branch.
joberstein Feb 16, 2023
ee480c7
chore(ci): Remove 'pre' property from action.
joberstein Feb 16, 2023
8adcea3
chore(tests): Move git config before init.
joberstein Feb 16, 2023
32118bc
Revert "chore(tests): Move git config before init."
joberstein Feb 16, 2023
360cee2
chore(ci): Set default git branch.
joberstein Feb 16, 2023
3914de8
fix: Replace actions/core with console calls.
joberstein Feb 16, 2023
109718e
chore: Add back @actions/core.
joberstein Feb 16, 2023
a1a1c0a
chore(tests): Fix spy in tests.
joberstein Feb 16, 2023
99f2780
chore(ci): Add debug statements.
joberstein Feb 16, 2023
fe54274
chore(ci): Remove debug.
joberstein Feb 16, 2023
6525bc5
chore: Add file that runs action.
joberstein Feb 17, 2023
a0762c1
chore(tests): Split up tests.
joberstein Feb 17, 2023
a213465
chore: Switch back to using core/actions from console.
joberstein Feb 17, 2023
42e8d82
chore: Add user information to the ci workflow.
joberstein Feb 17, 2023
1a0822d
chore: Add console debug logs.
joberstein Feb 17, 2023
b103f8a
chore(ci): Log branches.
joberstein Feb 17, 2023
8898e41
chore: Use github ref in action.
joberstein Feb 17, 2023
e6a4fd5
Revert "chore: Use github ref in action."
joberstein Mar 4, 2023
f4dd36b
chore: Add failure when 'getCommitFromRange' fails.
joberstein Mar 4, 2023
b5f2dd7
chore: Add more debug logs.
joberstein Mar 4, 2023
477040b
chore: Log rev-list result.
joberstein Mar 4, 2023
a00c21b
chore: Add more logging.
joberstein Mar 4, 2023
f693571
chore: Throw if git rev-list returns a blank result.
joberstein Mar 5, 2023
2c1a579
chore: Wrap source and destination branches for checkout in quotes.
joberstein Mar 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CI Pipeline

on:
push:
branches:
- '**'
pull_request:
types:
- opened
- reopened
- synchronize

jobs:
ci_pipeline:
runs-on: ubuntu-22.04
steps:
- name: Checkout Git Repository
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Setup NodeJS
uses: actions/setup-node@v3
with:
node-version: 18
cache: npm

- name: Install Dependencies
run: npm ci

- name: Build Application
run: npm run build

- name: Validate Commits
uses: ./
with:
base_ref: ${{ github.base_ref }}
head_ref: ${{ github.head_ref }}
target_ref: ${{ github.sha }}
extra_config: "@joberstein12/commitlint-config"

- name: Set default git branch
run: |
git config --global init.defaultBranch master
git config --global user.name "CI Pipeline"
git config --global user.email "[email protected]"

- name: Execute Tests
run: npm test -- --ci

- name: Deploy Application
uses: cycjimmy/semantic-release-action@v3
if: github.ref_name == 'master'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
13 changes: 1 addition & 12 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,8 @@ description: Validate commits with commitlint using Github Actions.
author: Jesse Oberstein
runs:
using: 'node16'
pre: 'dist/setup.js'
main: 'dist/index.js'
main: 'dist/action.js'
inputs:
commitlint_version:
description: The version of commitlint to use. If unspecified, the latest version will be installed.

config_path:
description: The relative path to the commitlint config file.

event:
description: The Github event that triggered the workflow that uses this action.
required: true

base_ref:
description: The source of pull request that triggered this workflow (required for pull requests).

Expand Down
3 changes: 3 additions & 0 deletions src/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import runAction from './index';

runAction();
2 changes: 1 addition & 1 deletion src/commitlint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import load from "@commitlint/load";
import read from "@commitlint/read";
import lint from "@commitlint/lint";
import format from "@commitlint/format";
import { info, warning, error } from "@actions/core";
import {info, warning, error} from "@actions/core";

/**
* Run commitlint and print the formatted results.
Expand Down
284 changes: 49 additions & 235 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,256 +1,70 @@
import { ExecSyncOptionsWithStringEncoding, execSync } from "child_process";
import run, { validateCommits } from "./index";
import preinstall from "./preinstall";
import * as commitlintExec from "./commitlint";
import { execSync } from "child_process";
import * as actions from "@actions/core";
import TestUtils from "./test/util";
import run from "./index";

describe("src/index", () => {
const encoding = 'utf-8';
const options: ExecSyncOptionsWithStringEncoding = { encoding };
const originalDirectory = process.cwd();
let tmpDir: string;

const commitlint = jest.spyOn(commitlintExec, 'default');
const actionsInfo = jest.spyOn(actions, "info");
const setFailed = jest.spyOn(actions, "setFailed");

const getNthCommitBack = (numBack: number) => execSync(`git rev-parse HEAD~${numBack - 1}`, options)
.trim();

const addValidCommit = () => {
const commands = [
"echo invalid >> src/test.txt",
"git add src",
`git commit -m "chore(ci): Add valid commit."`,
];

execSync(commands.join(" && "), options);
}
const {
createTempDirectory,
intializeGitRepo,
getNthCommitBack,
teardownGitRepo,
teardownTestDirectory,
addInvalidCommit,
addValidCommit,
setupTestDirectory,
options,
} = new TestUtils();

beforeEach(() => {
tmpDir = createTempDirectory();
setupTestDirectory(tmpDir);
intializeGitRepo();

process.env.INPUT_EXTRA_CONFIG = "\"@joberstein12/commitlint-config\"";
process.env.INPUT_TARGET_REF = getNthCommitBack(1);
});

const addInvalidCommit = () => {
const commands = [
"echo invalid >> src/test.txt",
"git add src",
`git commit -m "Add invalid commit."`,
];
afterEach(() => {
teardownGitRepo();
teardownTestDirectory(tmpDir);

execSync(commands.join(" && "), options);
}

const createTempDirectory = () => execSync("mktemp -d", { encoding })
.trim();

const setupTestDirectory = (tmpDir: string) => {
execSync(`cp ${process.cwd()}/.commitlintrc.json ${tmpDir}`);
execSync(`cp ${process.cwd()}/package*.json ${tmpDir}`);
process.chdir(tmpDir);
execSync('npm install --frozen-lockfile', options);
};

const intializeGitRepo = () => {
execSync([
"git init",
"rm -rf .git/hooks",
"mkdir src",
"touch src/test.txt",
].join(" && "), options);
delete process.env.INPUT_TARGET_REF;
delete process.env.INPUT_BASE_REF;
delete process.env.INPUT_HEAD_REF;
delete process.env.INPUT_EXTRA_CONFIG;

[ ...Array(2).keys() ].forEach(addValidCommit);
}

const teardownGitRepo = () => {
execSync([
"rm -rf .git",
"rm -rf src"
].join(' && '), options);
}

const teardownTestDirectory = (tmpDir: string) => {
process.chdir(originalDirectory);
execSync(`rm -rf ${tmpDir}`);
}

afterEach(() => {
expect(actionsInfo).toHaveBeenCalled();
});

describe("Integration Tests", () => {
let tmpDir: string;

beforeEach(() => {
tmpDir = createTempDirectory();
setupTestDirectory(tmpDir);
intializeGitRepo();

process.env.INPUT_EXTRA_CONFIG = "\"@joberstein12/commitlint-config\"";
process.env.INPUT_TARGET_REF = getNthCommitBack(1);
});

afterEach(() => {
teardownGitRepo();
teardownTestDirectory(tmpDir);

delete process.env.INPUT_TARGET_REF;
delete process.env.INPUT_BASE_REF;
delete process.env.INPUT_HEAD_REF;
delete process.env.INPUT_EXTRA_CONFIG;
})

it("Successfully validates a target commit", async () => {
await run();
expect(setFailed).not.toHaveBeenCalled();
});
it("Successfully validates a target commit", async () => {
await run();
expect(setFailed).not.toHaveBeenCalled();
});

it("Successfully validates a range of commits", async () => {
process.env.INPUT_BASE_REF = "master";
process.env.INPUT_HEAD_REF = "other";
it("Successfully validates a range of commits", async () => {
process.env.INPUT_BASE_REF = "master";
process.env.INPUT_HEAD_REF = "#3";

execSync("git checkout -qb other", options);
[ ...Array(3).keys() ].forEach(addValidCommit);
process.env.INPUT_TARGET_REF = getNthCommitBack(1);
execSync(`git checkout -qb '${process.env.INPUT_HEAD_REF}'`, options);
[ ...Array(3).keys() ].forEach(addValidCommit);
process.env.INPUT_TARGET_REF = getNthCommitBack(1);

await run();
expect(setFailed).not.toHaveBeenCalled();
});
await run();
expect(setFailed).not.toHaveBeenCalled();
});


it("Fails validation for an invalid commit", async () => {
addInvalidCommit();
process.env.INPUT_TARGET_REF = getNthCommitBack(1);
it("Fails validation for an invalid commit", async () => {
addInvalidCommit();
process.env.INPUT_TARGET_REF = getNthCommitBack(1);

await run();
expect(setFailed).toHaveBeenCalledWith('Commit validation failed.');
});
});

describe("Unit Tests", () => {
beforeAll(() => {
options.cwd = createTempDirectory();
setupTestDirectory(options.cwd);
preinstall('@joberstein12/commitlint-config', options);
});

beforeEach(() => {
intializeGitRepo();
});

afterEach(() => {
teardownGitRepo();
});

afterAll(() => {
teardownTestDirectory(options.cwd as string);
});

it("Validates all commits with empty args", async () => {
await expect(validateCommits({}, options)).resolves.not.toThrow();
expect(commitlint).toHaveBeenCalledWith(
expect.objectContaining({ from: undefined })
);
});

describe("Validating the target commit", () => {
it("Successfully validates the target commit", async () => {
const target = getNthCommitBack(1);
const args = { target };

await expect(validateCommits(args, options)).resolves.not.toThrow();
expect(commitlint).toHaveBeenCalledWith(
expect.objectContaining({ from: `${target}^` })
);
});

it("Fails when the target commit is invalid", async () => {
addInvalidCommit();

const target = getNthCommitBack(1);
const args = { target };

await expect(validateCommits(args, options)).rejects.toThrow();
expect(commitlint).toHaveBeenCalledWith(
expect.objectContaining({ from: `${target}^` })
);
});
});

describe("Validating a range of commits", () => {
beforeEach(() => {
execSync("git checkout -qb other", options);
[ ...Array(3).keys() ].forEach(addValidCommit);
});

it("Successfully completes commit validation", async () => {
const target = getNthCommitBack(3);
const args = {
source: "master",
destination: "other",
target,
};

await expect(validateCommits(args, options)).resolves.not.toThrow();
expect(commitlint).toHaveBeenCalledWith(
expect.objectContaining({ from: `${target}^` })
);
});

it("Fails when the commit range is invalid", async () => {
addInvalidCommit();
addValidCommit();

const target = getNthCommitBack(5);
const args = {
source: "master",
destination: "other",
target,
};

await expect(validateCommits(args, options)).rejects.toThrow();
expect(commitlint).toHaveBeenCalledWith(
expect.objectContaining({ from: `${target}^` })
);
});

it("Validates the target commit when the source is not known", async () => {
const target = getNthCommitBack(1);
const args = {
source: "error",
destination: "master",
target,
};

await expect(validateCommits(args, options)).resolves.not.toThrow();
expect(commitlint).toHaveBeenCalledWith(
expect.objectContaining({ from: `${target}^` })
);
});

it("Validates the target commit when the destination is not known", async () => {
const target = getNthCommitBack(1);
const args = {
source: "other",
destination: "error",
target,
};

await expect(validateCommits(args, options)).resolves.not.toThrow();
expect(commitlint).toHaveBeenCalledWith(
expect.objectContaining({ from: `${target}^` })
);
});

it("Successfully validates merge commits", async () => {
execSync([
'git checkout master',
'git merge --no-ff other'
].join(" && "), options);

const target = getNthCommitBack(1);
const args = { target };

await expect(validateCommits(args, options)).resolves.not.toThrow();
expect(commitlint).toHaveBeenCalledWith(
expect.objectContaining({ from: `${target}^` })
);
});
});
await run();
expect(setFailed).toHaveBeenCalledWith('Commit validation failed.');
});
});
Loading