-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathcommitlint.config.js
More file actions
97 lines (85 loc) · 3.64 KB
/
commitlint.config.js
File metadata and controls
97 lines (85 loc) · 3.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
const fs = require("fs");
const path = require("path");
// Dynamically discover and load all scopes from product config files
function loadScopes() {
const scopes = new Set();
const rootDir = __dirname;
// Find all Umbraco.AI* directories
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (!entry.name.startsWith("Umbraco.AI")) continue;
const configPath = path.join(rootDir, entry.name, "changelog.config.json");
// Load product scopes from changelog.config.json
if (fs.existsSync(configPath)) {
try {
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
if (config.scopes && Array.isArray(config.scopes)) {
config.scopes.forEach((scope) => scopes.add(scope));
}
} catch (err) {
console.warn(`Warning: Could not load scopes from ${configPath}:`, err.message);
}
}
}
// Add common meta scopes
scopes.add("deps"); // Dependency updates
scopes.add("ci"); // CI/CD changes
scopes.add("docs"); // Documentation
scopes.add("release"); // Release-related changes
scopes.add("hooks"); // Git hooks
scopes.add("build"); // Build system
scopes.add("config"); // Configuration files
return Array.from(scopes).sort();
}
module.exports = {
extends: ["@commitlint/config-conventional"],
plugins: [
{
rules: {
"scope-not-type": (parsed) => {
const { type, scope } = parsed;
if (scope && type && scope === type) {
return [
false,
`Scope "${scope}" should not match type "${type}". Use just "${type}:" without a scope, or use a more specific scope like "${type}(hooks):", "${type}(build):", etc.`,
];
}
return [true];
},
// Custom rule to validate multiple scopes (comma-separated)
"scope-enum-multiple": (parsed) => {
const { scope } = parsed;
if (!scope) {
return [true]; // No scope is handled by scope-empty rule
}
const allowedScopes = loadScopes();
const scopes = scope.split(",").map((s) => s.trim());
// Validate each scope against the allowed list
const invalidScopes = scopes.filter((s) => !allowedScopes.includes(s));
if (invalidScopes.length > 0) {
return [
false,
`Invalid scope(s): ${invalidScopes.join(", ")}. Allowed scopes: ${allowedScopes.join(", ")}`,
];
}
return [true];
},
},
},
],
rules: {
// Disable the default scope-enum rule (we use scope-enum-multiple instead)
"scope-enum": [0],
"scope-enum-multiple": [2, "always"], // Enable our custom multi-scope validation
"scope-empty": [1, "never"], // Warn if scope is missing (don't fail)
"scope-case": [2, "always", ["lower-case", "kebab-case"]],
"scope-not-type": [2, "always"], // Enable the custom rule
"subject-case": [2, "always", "sentence-case"],
"type-enum": [
2,
"always",
["feat", "fix", "docs", "chore", "refactor", "test", "perf", "ci", "revert", "build"],
],
},
};