Skip to content

Commit a99a92b

Browse files
committed
feat: initial commit
/spend 2h
0 parents  commit a99a92b

10 files changed

+2377
-0
lines changed

.gitignore

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Created by https://www.toptal.com/developers/gitignore/api/node
2+
# Edit at https://www.toptal.com/developers/gitignore?templates=node
3+
4+
### Node ###
5+
# Logs
6+
logs
7+
*.log
8+
npm-debug.log*
9+
yarn-debug.log*
10+
yarn-error.log*
11+
lerna-debug.log*
12+
.pnpm-debug.log*
13+
14+
# Diagnostic reports (https://nodejs.org/api/report.html)
15+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16+
17+
# Runtime data
18+
pids
19+
*.pid
20+
*.seed
21+
*.pid.lock
22+
23+
# Directory for instrumented libs generated by jscoverage/JSCover
24+
lib-cov
25+
26+
# Coverage directory used by tools like istanbul
27+
coverage
28+
*.lcov
29+
30+
# nyc test coverage
31+
.nyc_output
32+
33+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34+
.grunt
35+
36+
# Bower dependency directory (https://bower.io/)
37+
bower_components
38+
39+
# node-waf configuration
40+
.lock-wscript
41+
42+
# Compiled binary addons (https://nodejs.org/api/addons.html)
43+
build/Release
44+
45+
# Dependency directories
46+
node_modules/
47+
jspm_packages/
48+
49+
# Snowpack dependency directory (https://snowpack.dev/)
50+
web_modules/
51+
52+
# TypeScript cache
53+
*.tsbuildinfo
54+
55+
# Optional npm cache directory
56+
.npm
57+
58+
# Optional eslint cache
59+
.eslintcache
60+
61+
# Optional stylelint cache
62+
.stylelintcache
63+
64+
# Microbundle cache
65+
.rpt2_cache/
66+
.rts2_cache_cjs/
67+
.rts2_cache_es/
68+
.rts2_cache_umd/
69+
70+
# Optional REPL history
71+
.node_repl_history
72+
73+
# Output of 'npm pack'
74+
*.tgz
75+
76+
# Yarn Integrity file
77+
.yarn-integrity
78+
79+
# dotenv environment variable files
80+
.env
81+
.env.development.local
82+
.env.test.local
83+
.env.production.local
84+
.env.local
85+
86+
# parcel-bundler cache (https://parceljs.org/)
87+
.cache
88+
.parcel-cache
89+
90+
# Next.js build output
91+
.next
92+
out
93+
94+
# Nuxt.js build / generate output
95+
.nuxt
96+
dist
97+
98+
# Gatsby files
99+
.cache/
100+
# Comment in the public line in if your project uses Gatsby and not Next.js
101+
# https://nextjs.org/blog/next-9-1#public-directory-support
102+
# public
103+
104+
# vuepress build output
105+
.vuepress/dist
106+
107+
# vuepress v2.x temp and cache directory
108+
.temp
109+
110+
# Docusaurus cache and generated files
111+
.docusaurus
112+
113+
# Serverless directories
114+
.serverless/
115+
116+
# FuseBox cache
117+
.fusebox/
118+
119+
# DynamoDB Local files
120+
.dynamodb/
121+
122+
# TernJS port file
123+
.tern-port
124+
125+
# Stores VSCode versions used for testing VSCode extensions
126+
.vscode-test
127+
128+
# yarn v2
129+
.yarn/cache
130+
.yarn/unplugged
131+
.yarn/build-state.yml
132+
.yarn/install-state.gz
133+
.pnp.*
134+
135+
### Node Patch ###
136+
# Serverless Webpack directories
137+
.webpack/
138+
139+
# Optional stylelint cache
140+
141+
# SvelteKit build / generate output
142+
.svelte-kit
143+
144+
# End of https://www.toptal.com/developers/gitignore/api/node

.npmignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
commitlint.config.js
2+
node_modules

README.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# commitlint-plugin-spend
2+
3+
[Commitlint](https://commitlint.js.org/) plugin to enforce the use of [spend directives](https://docs.gitlab.com/ee/user/project/quick_actions.html#issues-merge-requests-and-epics) in commit messages.
4+
5+
## Available rules
6+
7+
### spend
8+
9+
- **condition**: `body` ends with a valid [spend directive](https://docs.gitlab.com/ee/user/project/quick_actions.html#issues-merge-requests-and-epics)
10+
- **rule**: `always`
11+
12+
## Usage
13+
14+
1. Install the plugin:
15+
16+
```bash
17+
npm i -D commitlint-plugin-spend
18+
```
19+
20+
```bash
21+
yarn add -D commitlint-plugin-spend
22+
```
23+
24+
```bash
25+
pnpm add -D commitlint-plugin-spend
26+
```
27+
28+
```bash
29+
bun add -d commitlint-plugin-spend
30+
```
31+
32+
2. Add the plugin to your `commitlint` configuration:
33+
34+
```json
35+
{
36+
"extends": ["@commitlint/config-conventional"],
37+
"plugins": ["commitlint-plugin-spend"]
38+
}
39+
```
40+
41+
3. Configure the `spend` rule in your `commitlint` configuration:
42+
43+
```json
44+
{
45+
"rules": {
46+
"spend": [2, "always"]
47+
}
48+
}
49+
```
50+
51+
## License
52+
53+
[MIT](./LICENSE)

commitlint.config.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
extends: ["@commitlint/config-conventional"],
3+
plugins: ["commitlint-plugin-spend"],
4+
rules: {
5+
spend: [2, "always"],
6+
},
7+
};

index.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { validateTimeUnit } from "./src/types/TimeUnit";
2+
3+
/**
4+
* The GitLab spend directives.
5+
* @see https://docs.gitlab.com/ee/user/project/quick_actions.html#issues-merge-requests-and-epics
6+
*/
7+
const spendDirectives = ["/spend", "/spend_time"];
8+
9+
module.exports = {
10+
rules: {
11+
spend: (ctx, applicable) => {
12+
if (applicable === "never") {
13+
return [false, 'the "spend" rule does not support "never"'];
14+
}
15+
16+
const { body } = ctx;
17+
18+
if (!body) {
19+
// Require a body
20+
return [false, "Commit message body must contain a spend directive"];
21+
}
22+
23+
const lines = body.split("\n");
24+
25+
const spendLines = lines.filter((line) =>
26+
spendDirectives.some((directive) => line.startsWith(directive))
27+
);
28+
29+
if (spendLines.length === 0) {
30+
return [
31+
false,
32+
"Commit message body must contain a line starting with a spend directive",
33+
];
34+
}
35+
36+
for (const line of spendLines) {
37+
const lineSplit = line.split(" ");
38+
39+
if (lineSplit.length < 2) {
40+
return [
41+
false,
42+
"Spend directive must be provided with at least one time value",
43+
];
44+
}
45+
46+
const timeValues = lineSplit.slice(1);
47+
48+
const [isValid, msg] = validateTimeUnit(timeValues);
49+
50+
if (!isValid) {
51+
return [false, msg];
52+
}
53+
}
54+
55+
return [true, undefined];
56+
},
57+
},
58+
};

index.test.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, test } from "vitest";
2+
import { rules } from "./index.js";
3+
4+
describe("commitlint-plugin-spend", () => {
5+
const { spend } = rules;
6+
const alwaysApplicable = "always";
7+
8+
test("should return false if the applicable value is 'never'", () => {
9+
const ctx = { body: "body" };
10+
const applicable = "never";
11+
const [isValid, error] = spend(ctx, applicable);
12+
expect(isValid).toBe(false);
13+
expect(error).toBe('the "spend" rule does not support "never"');
14+
});
15+
16+
test("should return false if the body is empty", () => {
17+
const ctx = { body: "" };
18+
const [isValid, error] = spend(ctx, alwaysApplicable);
19+
expect(isValid).toBe(false);
20+
expect(error).toBe("Commit message body must contain a spend directive");
21+
});
22+
23+
test("should return false if the body does not contain a spend directive", () => {
24+
const ctx = { body: "body" };
25+
const [isValid, error] = spend(ctx, alwaysApplicable);
26+
expect(isValid).toBe(false);
27+
expect(error).toBe(
28+
"Commit message body must contain a line starting with a spend directive"
29+
);
30+
});
31+
32+
test("should return false if the spend directive does not contain a time value", () => {
33+
const ctx = { body: "/spend" };
34+
const [isValid, error] = spend(ctx, alwaysApplicable);
35+
expect(isValid).toBe(false);
36+
expect(error).toBe(
37+
"Spend directive must be provided with at least one time value"
38+
);
39+
});
40+
41+
test("should support alternative spend directive", () => {
42+
const ctx = { body: "/spend_time" };
43+
const [isValid, error] = spend(ctx, alwaysApplicable);
44+
expect(isValid).toBe(false);
45+
expect(error).toBe(
46+
"Spend directive must be provided with at least one time value"
47+
);
48+
});
49+
50+
test("should return true for valid time values", () => {
51+
const ctx = { body: "/spend 2d 3h 30m" };
52+
const [isValid, error] = spend(ctx, alwaysApplicable);
53+
expect(error).toBeUndefined();
54+
expect(isValid).toBe(true);
55+
});
56+
});

package.json

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "commitlint-plugin-spend",
3+
"version": "1.0.1",
4+
"description": "A commitlint plugin that requires the GitLab /spend or /spend_time directive in commit messages",
5+
"main": "index.js",
6+
"keywords": [
7+
"commitlint",
8+
"commitlintplugin",
9+
"git",
10+
"lab",
11+
"commit",
12+
"spend",
13+
"message",
14+
"time",
15+
"tracking"
16+
],
17+
"author": "Nico Ismaili",
18+
"license": "MIT",
19+
"homepage": "https://github.com/nico-i/commitlint-plugin-spend",
20+
"repository": {
21+
"type": "git",
22+
"url": "https://github.com/nico-i/commitlint-plugin-spend.git"
23+
},
24+
"scripts": {
25+
"test": "vitest"
26+
},
27+
"peerDependencies": {
28+
"@commitlint/lint": ">=7.6.0"
29+
},
30+
"devDependencies": {
31+
"@commitlint/core": "^18.6.0",
32+
"@types/node": "^20.11.16",
33+
"commitlint-plugin-spend": "link:",
34+
"vitest": "^1.2.2"
35+
}
36+
}

0 commit comments

Comments
 (0)