@naturalcycles/dev-lib 20.17.0 → 20.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cfg/oxlint.config.json +1 -0
- package/dist/bin/dev-lib.js +3 -2
- package/dist/commitlint.d.ts +15 -0
- package/dist/commitlint.js +106 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +20 -0
- package/dist/lint.util.d.ts +0 -1
- package/dist/lint.util.js +0 -19
- package/package.json +1 -3
package/cfg/oxlint.config.json
CHANGED
package/dist/bin/dev-lib.js
CHANGED
|
@@ -5,7 +5,8 @@ import { _assert } from '@naturalcycles/js-lib/error/assert.js';
|
|
|
5
5
|
import { fs2 } from '@naturalcycles/nodejs-lib/fs2';
|
|
6
6
|
import { runScript } from '@naturalcycles/nodejs-lib/runScript';
|
|
7
7
|
import { buildCopy, buildProd, runTSCInFolders } from '../build.util.js';
|
|
8
|
-
import {
|
|
8
|
+
import { runCommitlint } from '../commitlint.js';
|
|
9
|
+
import { eslintAll, lintAllCommand, lintStagedCommand, requireOxlintConfig, runBiome, runOxlint, runPrettier, stylelintAll, } from '../lint.util.js';
|
|
9
10
|
import { runTest } from '../test.util.js';
|
|
10
11
|
const commands = [
|
|
11
12
|
new Separator(), // build
|
|
@@ -105,7 +106,7 @@ const commands = [
|
|
|
105
106
|
desc: 'Run stylelint with auto-fix disabled.',
|
|
106
107
|
},
|
|
107
108
|
{ name: 'stylelint-no-fix', cliOnly: true, fn: () => stylelintAll(false) },
|
|
108
|
-
{ name: 'commitlint', fn:
|
|
109
|
+
{ name: 'commitlint', fn: runCommitlint, desc: 'Run commitlint.', cliOnly: true },
|
|
109
110
|
new Separator(), // interactive-only
|
|
110
111
|
{
|
|
111
112
|
name: 'exit',
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type DevLibCommitlintConfig } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validates the commit message,
|
|
4
|
+
* which is read from a file, passed as process.argv.at(-1)
|
|
5
|
+
*/
|
|
6
|
+
export declare function runCommitlint(): void;
|
|
7
|
+
/**
|
|
8
|
+
* Commit message validator following Conventional Commits specification.
|
|
9
|
+
* https://www.conventionalcommits.org/
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateCommitMessage(input: string, cfg?: DevLibCommitlintConfig): CommitMessageValidationResponse;
|
|
12
|
+
export interface CommitMessageValidationResponse {
|
|
13
|
+
valid: boolean;
|
|
14
|
+
errors: string[];
|
|
15
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { _assert } from '@naturalcycles/js-lib/error';
|
|
2
|
+
import { fs2 } from '@naturalcycles/nodejs-lib/fs2';
|
|
3
|
+
import { readDevLibConfigIfPresent } from './config.js';
|
|
4
|
+
const ALLOWED_TYPES = new Set([
|
|
5
|
+
'feat',
|
|
6
|
+
'fix',
|
|
7
|
+
'chore',
|
|
8
|
+
'refactor',
|
|
9
|
+
'docs',
|
|
10
|
+
'style',
|
|
11
|
+
'test',
|
|
12
|
+
'perf',
|
|
13
|
+
'ci',
|
|
14
|
+
'build',
|
|
15
|
+
'revert',
|
|
16
|
+
]);
|
|
17
|
+
const SUBJECT_MAX_LENGTH = 120; // Only applies to subject line (first line)
|
|
18
|
+
/**
|
|
19
|
+
* Validates the commit message,
|
|
20
|
+
* which is read from a file, passed as process.argv.at(-1)
|
|
21
|
+
*/
|
|
22
|
+
export function runCommitlint() {
|
|
23
|
+
// || '.git/COMMIT_EDITMSG' // fallback is unnecessary, first argument should be always present
|
|
24
|
+
const arg1 = process.argv.at(-1);
|
|
25
|
+
_assert(arg1, 'dev-lib commitlint2 is called with $1 (first argument) missing');
|
|
26
|
+
console.log({ arg1 });
|
|
27
|
+
fs2.requireFileToExist(arg1);
|
|
28
|
+
const msg = fs2.readText(arg1);
|
|
29
|
+
console.log({ msg });
|
|
30
|
+
const devLibCfg = readDevLibConfigIfPresent();
|
|
31
|
+
console.log({ devLibCfg });
|
|
32
|
+
const { valid, errors } = validateCommitMessage(msg, devLibCfg.commitlint);
|
|
33
|
+
if (valid) {
|
|
34
|
+
console.log('✓ Valid commit message');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.error('✗ Invalid commit message:');
|
|
38
|
+
for (const err of errors) {
|
|
39
|
+
console.error(` - ${err}`);
|
|
40
|
+
}
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Commit message validator following Conventional Commits specification.
|
|
45
|
+
* https://www.conventionalcommits.org/
|
|
46
|
+
*/
|
|
47
|
+
export function validateCommitMessage(input, cfg = {}) {
|
|
48
|
+
const errors = [];
|
|
49
|
+
const msg = input.trim();
|
|
50
|
+
if (!msg) {
|
|
51
|
+
return { valid: false, errors: ['Commit message is empty'] };
|
|
52
|
+
}
|
|
53
|
+
const lines = msg.split('\n');
|
|
54
|
+
const subjectLine = lines[0];
|
|
55
|
+
// Step 1: Validate subject line format
|
|
56
|
+
// Pattern: type(scope)!: description OR type!: description OR type(scope): description OR type: description
|
|
57
|
+
const subjectPattern = /^(\w+)(?:\(([^)]+)\))?(!)?\s*:\s*(.+)$/;
|
|
58
|
+
const match = subjectLine.match(subjectPattern);
|
|
59
|
+
if (!match) {
|
|
60
|
+
errors.push(`Subject line must match format: type(scope): description\n` +
|
|
61
|
+
` Got: "${subjectLine}"\n` +
|
|
62
|
+
` Examples: "feat(auth): add login", "fix: resolve crash"`);
|
|
63
|
+
return { valid: false, errors };
|
|
64
|
+
}
|
|
65
|
+
const [, type, scope, _breaking, description] = match;
|
|
66
|
+
// Step 2: Validate type
|
|
67
|
+
if (!ALLOWED_TYPES.has(type)) {
|
|
68
|
+
errors.push(`Invalid type "${type}". Allowed types: ${[...ALLOWED_TYPES].join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
// Step 3: Validate subject line length
|
|
71
|
+
if (subjectLine.length > SUBJECT_MAX_LENGTH) {
|
|
72
|
+
errors.push(`Subject line too long: ${subjectLine.length} chars (max ${SUBJECT_MAX_LENGTH})`);
|
|
73
|
+
}
|
|
74
|
+
// Step 4: Validate description is not empty
|
|
75
|
+
if (!description?.trim()) {
|
|
76
|
+
errors.push('Description after colon cannot be empty');
|
|
77
|
+
}
|
|
78
|
+
// Step 5: Validate description doesn't start with capital letter (conventional style)
|
|
79
|
+
// Disabled: many existing commits use capitals
|
|
80
|
+
// if (description && /^[A-Z]/.test(description.trim())) {
|
|
81
|
+
// errors.push('Description should start with lowercase letter')
|
|
82
|
+
// }
|
|
83
|
+
// Step 6: Validate blank line between subject and body (if body exists)
|
|
84
|
+
if (lines.length > 1 && lines[1].trim() !== '') {
|
|
85
|
+
errors.push('There must be a blank line between subject and body');
|
|
86
|
+
}
|
|
87
|
+
// Note: No line length validation for body lines - they can be any length
|
|
88
|
+
// Step 7: scope validation
|
|
89
|
+
if (cfg.requireScope && !scope) {
|
|
90
|
+
errors.push('Scope is required');
|
|
91
|
+
}
|
|
92
|
+
if (scope && cfg.allowedScopes && !cfg.allowedScopes.includes(scope)) {
|
|
93
|
+
errors.push(`Scope must be one of the allowed scopes:\n${cfg.allowedScopes.join('\n')}`);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
valid: errors.length === 0,
|
|
97
|
+
errors,
|
|
98
|
+
// parsed: {
|
|
99
|
+
// type,
|
|
100
|
+
// scope: scope || null,
|
|
101
|
+
// breaking: !!breaking,
|
|
102
|
+
// description: description?.trim(),
|
|
103
|
+
// body: lines.slice(2).join('\n').trim() || null,
|
|
104
|
+
// },
|
|
105
|
+
};
|
|
106
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns an empty config if the file is absent.
|
|
3
|
+
*/
|
|
4
|
+
export declare function readDevLibConfigIfPresent(): DevLibConfig;
|
|
5
|
+
export interface DevLibConfig {
|
|
6
|
+
commitlint?: DevLibCommitlintConfig;
|
|
7
|
+
}
|
|
8
|
+
export interface DevLibCommitlintConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Defaults to false.
|
|
11
|
+
* If set to true - commit scope becomes required.
|
|
12
|
+
*/
|
|
13
|
+
requireScope?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* If defined - commitlint (which is run on git precommit hook) will validate that
|
|
16
|
+
* the scope is one of the allowedScopes.
|
|
17
|
+
* Empty (not present) scope will pass this rule, as it depends on the `requireScope` option instead.
|
|
18
|
+
*/
|
|
19
|
+
allowedScopes?: string[];
|
|
20
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AjvSchema, j } from '@naturalcycles/nodejs-lib/ajv';
|
|
2
|
+
import { fs2 } from '@naturalcycles/nodejs-lib/fs2';
|
|
3
|
+
const devLibConfigPath = 'dev-lib.config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Returns an empty config if the file is absent.
|
|
6
|
+
*/
|
|
7
|
+
export function readDevLibConfigIfPresent() {
|
|
8
|
+
const cfg = fs2.pathExists(devLibConfigPath) ? fs2.readJson(devLibConfigPath) : {};
|
|
9
|
+
return devLibConfigSchema.validate(cfg);
|
|
10
|
+
}
|
|
11
|
+
const devLibConfigSchema = AjvSchema.create(j.object({
|
|
12
|
+
commitlint: j.object
|
|
13
|
+
.infer({
|
|
14
|
+
requireScope: j.boolean().optional(),
|
|
15
|
+
allowedScopes: j.array(j.string()).optional(),
|
|
16
|
+
})
|
|
17
|
+
.optional(),
|
|
18
|
+
}), {
|
|
19
|
+
inputName: 'dev-lib.config.js',
|
|
20
|
+
});
|
package/dist/lint.util.d.ts
CHANGED
|
@@ -21,7 +21,6 @@ interface RunPrettierOptions {
|
|
|
21
21
|
export declare function runPrettier(opt?: RunPrettierOptions): void;
|
|
22
22
|
export declare function stylelintAll(fix?: boolean): void;
|
|
23
23
|
export declare function lintStagedCommand(): Promise<void>;
|
|
24
|
-
export declare function runCommitlintCommand(): void;
|
|
25
24
|
export declare function requireActionlintVersion(): void;
|
|
26
25
|
export declare function getActionLintVersion(): SemVerString | undefined;
|
|
27
26
|
export declare function runBiome(fix?: boolean): void;
|
package/dist/lint.util.js
CHANGED
|
@@ -7,7 +7,6 @@ import { _since } from '@naturalcycles/js-lib/datetime/time.util.js';
|
|
|
7
7
|
import { _assert } from '@naturalcycles/js-lib/error/assert.js';
|
|
8
8
|
import { _filterFalsyValues } from '@naturalcycles/js-lib/object/object.util.js';
|
|
9
9
|
import { semver2 } from '@naturalcycles/js-lib/semver';
|
|
10
|
-
import { git2 } from '@naturalcycles/nodejs-lib';
|
|
11
10
|
import { dimGrey, white } from '@naturalcycles/nodejs-lib/colors';
|
|
12
11
|
import { exec2 } from '@naturalcycles/nodejs-lib/exec2';
|
|
13
12
|
import { fs2 } from '@naturalcycles/nodejs-lib/fs2';
|
|
@@ -231,24 +230,6 @@ export async function lintStagedCommand() {
|
|
|
231
230
|
if (!success)
|
|
232
231
|
process.exit(3);
|
|
233
232
|
}
|
|
234
|
-
export function runCommitlintCommand() {
|
|
235
|
-
const editMsg = process.argv.at(-1) || '.git/COMMIT_EDITMSG';
|
|
236
|
-
// console.log(editMsg)
|
|
237
|
-
const cwd = process.cwd();
|
|
238
|
-
const localConfig = `${cwd}/commitlint.config.js`;
|
|
239
|
-
const sharedConfig = `${cfgDir}/commitlint.config.js`;
|
|
240
|
-
const config = existsSync(localConfig) ? localConfig : sharedConfig;
|
|
241
|
-
const env = {
|
|
242
|
-
GIT_BRANCH: git2.getCurrentBranchName(),
|
|
243
|
-
};
|
|
244
|
-
const commitlintPath = findPackageBinPath('@commitlint/cli', 'commitlint');
|
|
245
|
-
exec2.spawn(`${commitlintPath} --edit ${editMsg} --config ${config}`, {
|
|
246
|
-
env,
|
|
247
|
-
passProcessEnv: true, // important to pass it through, to preserve $PATH
|
|
248
|
-
forceColor: false,
|
|
249
|
-
log: false,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
233
|
async function runKTLint(fix = true) {
|
|
253
234
|
if (!existsSync(`node_modules/@naturalcycles/ktlint`))
|
|
254
235
|
return;
|
package/package.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/dev-lib",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "20.
|
|
4
|
+
"version": "20.18.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@biomejs/biome": "^2",
|
|
7
|
-
"@commitlint/cli": "^20",
|
|
8
|
-
"@commitlint/config-conventional": "^20",
|
|
9
7
|
"@eslint/js": "^9",
|
|
10
8
|
"@inquirer/prompts": "^8",
|
|
11
9
|
"@naturalcycles/js-lib": "^15",
|