Skip to content

Commit

Permalink
feat(turbo-ignore): check commit body (vercel#2835)
Browse files Browse the repository at this point in the history
  • Loading branch information
tknickman authored and elitan committed Dec 6, 2022
1 parent 51ca4e6 commit bf8016a
Show file tree
Hide file tree
Showing 13 changed files with 586 additions and 90 deletions.
38 changes: 24 additions & 14 deletions packages/turbo-ignore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ Flags:
disable [default: HEAD^]
--help, -h Show this help message
--version, -v Show the version of this script

---

turbo-ignore will also check for special commit messages to indicate if a build should be skipped or not.

Skip turbo-ignore check and automatically ignore:
- [skip ci]
- [ci skip]
- [no ci]
- [skip vercel]
- [vercel skip]
- [vercel skip <workspace>]

Skip turbo-ignore check and automatically deploy:
- [vercel deploy]
- [vercel build]
- [vercel deploy <workspace>]
- [vercel build <workspace>]
```
### Examples
Expand All @@ -37,39 +55,31 @@ Flags:
npx turbo-ignore
```
> Only build if there are changes to the workspace in the current working directory, or any of it's dependencies. On Vercel, compare against the last successful deployment for the current branch. If this does not exist (first deploy of the branch), compare against the previous commit. When not on Vercel, always compare against the previous commit.
> Only build if there are changes to the workspace in the current working directory, or any of it's dependencies. On Vercel, compare against the last successful deployment for the current branch. When not on Vercel, compare against the parent commit (`HEAD^`).
---
```sh
npx turbo-ignore docs
```
> Only build if there are changes to the `docs` workspace, or any of it's dependencies. On Vercel, compare against the last successful deployment for the current branch. If this does not exist (first deploy of the branch), compare against the previous commit. When not on Vercel, always compare against the previous commit.
---
```sh
npx turbo-ignore --fallback=false
```
> Only build if there are changes to the workspace in the current working directory, or any of it's dependencies. On Vercel, compare against the last successful deployment for the current branch. If this does not exist, (first deploy of the branch), the build will proceed. When not on Vercel, always compare against the previous commit.
> Only build if there are changes to the `docs` workspace, or any of its dependencies. On Vercel, compare against the last successful deployment for the current branch. When not on Vercel compare against the parent commit (`HEAD^`).
---
```sh
npx turbo-ignore --fallback=HEAD~10
```
> Only build if there are changes to the workspace in the current working directory, or any of it's dependencies. On Vercel, compare against the last successful deployment for the current branch. If this does not exist (first deploy of the branch), compare against the previous 10 commits. When not on Vercel, always compare against the previous commit.
> Only build if there are changes to the workspace in the current working directory, or any of it's dependencies. On Vercel, compare against the last successful deployment for the current branch. If this does not exist (first deploy of the branch), compare against the previous 10 commits. When not on Vercel, always compare against the parent commit (`HEAD^`).
---
```sh
npx turbo-ignore app --fallback=develop
npx turbo-ignore --fallback=HEAD^
```
> Only build if there are changes to the `app` workspace, or any of it's dependencies. On Vercel, compare against the last successful deployment for the current branch. If this does not exist (first deploy of the branch), compare against the `develop` branch. When not on Vercel, always compare against the previous commit.
> Only build if there are changes to the workspace in the current working directory, or any of it's dependencies. On Vercel, compare against the last successful deployment for the current branch. If this does not exist (first deploy of the branch), compare against the parent commit (`HEAD^`). When not on Vercel, always compare against the parent commit (`HEAD^`).
## How it Works
Expand All @@ -83,7 +93,7 @@ Next, it uses `turbo run build --dry` to determine if the given workspace, _or a
When deploying on [Vercel](https://vercel.com), `turbo-ignore` can make a more accurate decision by comparing between the current commit, and the last successfully deployed commit for the current branch.
**NOTE:** By default on Vercel, if the branch has not been deployed, `turbo-ignore` will fall back to comparing against the previous commit. To always deploy the first commit of a new branch, this fallback behavior can be disabled with `--fallback=false`, or customized by providing the `ref` to compare against (defaults to `HEAD^1`).
**NOTE:** By default on Vercel, `turbo-ignore` will always deploy the first commit of a new branch. This behavior can be changed by providing the `ref` to compare against to the `--fallback` flag. See the [Examples](#Examples) section for more details.
## Releasing
Expand Down
8 changes: 4 additions & 4 deletions packages/turbo-ignore/__tests__/args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ describe("parseArgs()", () => {
});

it("correctly finds fallback", async () => {
const result = parseArgs({ argv: ["--fallback=false"] });
const result = parseArgs({ argv: ["--fallback=HEAD^"] });
expect(result.workspace).toBe(undefined);
expect(result.fallback).toBe("false");
expect(result.fallback).toBe("HEAD^");
expect(mockExit.exit).toHaveBeenCalledTimes(0);
});

Expand All @@ -59,10 +59,10 @@ describe("parseArgs()", () => {

it("correctly finds fallback and workspace", async () => {
const result = parseArgs({
argv: ["this-workspace", "--fallback=false"],
argv: ["this-workspace", "--fallback=HEAD~10"],
});
expect(result.workspace).toBe("this-workspace");
expect(result.fallback).toBe("false");
expect(result.fallback).toBe("HEAD~10");
expect(mockExit.exit).toHaveBeenCalledTimes(0);
});
});
229 changes: 229 additions & 0 deletions packages/turbo-ignore/__tests__/checkCommit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import child_process, { ChildProcess, ExecException } from "child_process";
import { checkCommit } from "../src/checkCommit";
import { mockEnv } from "./test-utils";

describe("checkCommit()", () => {
describe("on Vercel", () => {
mockEnv();

describe("for all workspaces", () => {
it("results in continue when no special commit messages are found", async () => {
process.env.VERCEL = "1";
process.env.VERCEL_GIT_COMMIT_MESSAGE = "fixing a test";
expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "continue",
scope: "global",
reason: "no deploy or skip string found in commit message.",
});
});

it("results in conflict when deploy and skip commit messages are found", async () => {
process.env.VERCEL = "1";
process.env.VERCEL_GIT_COMMIT_MESSAGE =
"deploying [vercel deploy] and skipping [vercel skip]";
expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "conflict",
scope: "global",
reason:
"conflicting commit messages found: [vercel deploy] and [vercel skip]",
});
});

it("results in deploy when deploy commit message is found", async () => {
process.env.VERCEL = "1";
process.env.VERCEL_GIT_COMMIT_MESSAGE = "deploying [vercel deploy]";
expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "deploy",
scope: "global",
reason: "found commit message: [vercel deploy]",
});
});

it("results in skip when skip commit message is found", async () => {
process.env.VERCEL = "1";
process.env.VERCEL_GIT_COMMIT_MESSAGE = "skip deployment [vercel skip]";
expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "skip",
scope: "global",
reason: "found commit message: [vercel skip]",
});
});
});

describe("for specific workspaces", () => {
it("results in continue when no special commit messages are found", async () => {
process.env.VERCEL = "1";
process.env.VERCEL_GIT_COMMIT_MESSAGE =
"fixing a test in test-workspace";
expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "continue",
scope: "global",
reason: "no deploy or skip string found in commit message.",
});
});

it("results in conflict when deploy and skip commit messages are found", async () => {
process.env.VERCEL = "1";
process.env.VERCEL_GIT_COMMIT_MESSAGE =
"deploying [vercel deploy test-workspace] and skipping [vercel skip test-workspace]";
expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "conflict",
scope: "workspace",
reason:
"conflicting commit messages found: [vercel deploy test-workspace] and [vercel skip test-workspace]",
});
});

it("results in deploy when deploy commit message is found", async () => {
process.env.VERCEL = "1";
process.env.VERCEL_GIT_COMMIT_MESSAGE =
"deploying [vercel deploy test-workspace]";
expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "deploy",
scope: "workspace",
reason: "found commit message: [vercel deploy test-workspace]",
});
});

it("results in skip when skip commit message is found", async () => {
process.env.VERCEL = "1";
process.env.VERCEL_GIT_COMMIT_MESSAGE =
"skip deployment [vercel skip test-workspace]";
expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "skip",
scope: "workspace",
reason: "found commit message: [vercel skip test-workspace]",
});
});
});
});
describe("Not on Vercel", () => {
describe("for all workspaces", () => {
it("results in continue when no special commit messages are found", async () => {
const commitBody = "fixing a test";
const mockExecSync = jest
.spyOn(child_process, "execSync")
.mockImplementation((_) => commitBody);

expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "continue",
scope: "global",
reason: "no deploy or skip string found in commit message.",
});
expect(mockExecSync).toHaveBeenCalledWith("git show -s --format=%B");
mockExecSync.mockRestore();
});

it("results in conflict when deploy and skip commit messages are found", async () => {
const commitBody =
"deploying [vercel deploy] and skipping [vercel skip]";
const mockExecSync = jest
.spyOn(child_process, "execSync")
.mockImplementation((_) => commitBody);

expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "conflict",
scope: "global",
reason:
"conflicting commit messages found: [vercel deploy] and [vercel skip]",
});
expect(mockExecSync).toHaveBeenCalledWith("git show -s --format=%B");
mockExecSync.mockRestore();
});

it("results in deploy when deploy commit message is found", async () => {
const commitBody = "deploying [vercel deploy]";
const mockExecSync = jest
.spyOn(child_process, "execSync")
.mockImplementation((_) => commitBody);

expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "deploy",
scope: "global",
reason: "found commit message: [vercel deploy]",
});
expect(mockExecSync).toHaveBeenCalledWith("git show -s --format=%B");
mockExecSync.mockRestore();
});

it("results in skip when skip commit message is found", async () => {
const commitBody = "skip deployment [vercel skip]";
const mockExecSync = jest
.spyOn(child_process, "execSync")
.mockImplementation((_) => commitBody);

expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "skip",
scope: "global",
reason: "found commit message: [vercel skip]",
});
expect(mockExecSync).toHaveBeenCalledWith("git show -s --format=%B");
mockExecSync.mockRestore();
});
});

describe("for specific workspaces", () => {
it("results in continue when no special commit messages are found", async () => {
const commitBody = "fixing a test in test-workspace";
const mockExecSync = jest
.spyOn(child_process, "execSync")
.mockImplementation((_) => commitBody);

expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "continue",
scope: "global",
reason: "no deploy or skip string found in commit message.",
});
expect(mockExecSync).toHaveBeenCalledWith("git show -s --format=%B");
mockExecSync.mockRestore();
});

it("results in conflict when deploy and skip commit messages are found", async () => {
const commitBody =
"deploying [vercel deploy test-workspace] and skipping [vercel skip test-workspace]";
const mockExecSync = jest
.spyOn(child_process, "execSync")
.mockImplementation((_) => commitBody);

expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "conflict",
scope: "workspace",
reason:
"conflicting commit messages found: [vercel deploy test-workspace] and [vercel skip test-workspace]",
});
expect(mockExecSync).toHaveBeenCalledWith("git show -s --format=%B");
mockExecSync.mockRestore();
});

it("results in deploy when deploy commit message is found", async () => {
const commitBody = "deploying [vercel deploy test-workspace]";
const mockExecSync = jest
.spyOn(child_process, "execSync")
.mockImplementation((_) => commitBody);

expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "deploy",
scope: "workspace",
reason: "found commit message: [vercel deploy test-workspace]",
});
expect(mockExecSync).toHaveBeenCalledWith("git show -s --format=%B");
mockExecSync.mockRestore();
});

it("results in skip when skip commit message is found", async () => {
const commitBody = "skip deployment [vercel skip test-workspace]";
const mockExecSync = jest
.spyOn(child_process, "execSync")
.mockImplementation((_) => commitBody);

expect(checkCommit({ workspace: "test-workspace" })).toEqual({
result: "skip",
scope: "workspace",
reason: "found commit message: [vercel skip test-workspace]",
});
expect(mockExecSync).toHaveBeenCalledWith("git show -s --format=%B");
mockExecSync.mockRestore();
});
});
});
});
35 changes: 7 additions & 28 deletions packages/turbo-ignore/__tests__/getComparison.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { getComparison } from "../src/getComparison";
import { spyConsole, validateLogs } from "./test-utils";
import { spyConsole, validateLogs, mockEnv } from "./test-utils";

describe("getComparison()", () => {
mockEnv();
const mockConsole = spyConsole();
it("uses headRelative comparison when not running Vercel CI", async () => {
expect(getComparison({ workspace: "test-workspace" }))
Expand All @@ -13,35 +14,13 @@ describe("getComparison()", () => {
`);
});

it("returns null when running in Vercel CI with no VERCEL_GIT_PREVIOUS_SHA and fallback disabled", async () => {
it("returns null when running in Vercel CI with no VERCEL_GIT_PREVIOUS_SHA", async () => {
process.env.VERCEL = "1";
process.env.VERCEL_GIT_COMMIT_REF = "my-branch";
expect(
getComparison({ workspace: "test-workspace", fallback: "false" })
).toBeNull();
expect(getComparison({ workspace: "test-workspace" })).toBeNull();
expect(mockConsole.log).toHaveBeenCalledWith(
"≫ ",
'no previous deployments found for "test-workspace" on "my-branch".'
);
});

it("uses default fallback when running in Vercel CI with no VERCEL_GIT_PREVIOUS_SHA", async () => {
process.env.VERCEL = "1";
process.env.VERCEL_GIT_COMMIT_REF = "my-branch";
expect(getComparison({ workspace: "test-workspace" }))
.toMatchInlineSnapshot(`
Object {
"ref": "HEAD^",
"type": "customFallback",
}
`);

validateLogs(
[
'no previous deployments found for "test-workspace" on "my-branch".',
"falling back to HEAD^",
],
mockConsole.log
'no previous deployments found for "test-workspace" on branch "my-branch".'
);
});

Expand All @@ -58,12 +37,12 @@ describe("getComparison()", () => {
expect(mockConsole.log).toHaveBeenNthCalledWith(
1,
"≫ ",
'no previous deployments found for "test-workspace" on "my-branch".'
'no previous deployments found for "test-workspace" on branch "my-branch".'
);
expect(mockConsole.log).toHaveBeenNthCalledWith(
2,
"≫ ",
"falling back to HEAD^2"
"falling back to ref HEAD^2"
);
});

Expand Down

0 comments on commit bf8016a

Please sign in to comment.