commitlint-config-spellbookx 1.1.1 → 1.1.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # commitlint-config-spellbookx
2
2
 
3
+ ## 1.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - chore(release): automated patch release bump
8
+
3
9
  ## 1.1.1
4
10
 
5
11
  ### Patch Changes
@@ -1,9 +1,4 @@
1
- /**
2
- * @file index.test.ts
3
- * Tests verifying the main configuration export and metadata of commitlint-config-spellbookx.
4
- */
5
-
6
- import { describe, expect, test } from 'vitest';
1
+ import { describe, expect, it } from 'vitest';
7
2
 
8
3
  import defaultExport, {
9
4
  COMMIT_TYPES,
@@ -17,12 +12,11 @@ import defaultExport, {
17
12
  } from '../src/index.js';
18
13
  import { packageJson } from '../src/lib/package-json.js';
19
14
 
20
- describe('commitlint-config-spellbookx entrypoint', () => {
21
- test('should export valid configuration object', () => {
15
+ describe('commitlint-config-spellbookx (entrypoint)', () => {
16
+ it('exports a valid commitlint UserConfig object as default and named export', () => {
22
17
  expect(defaultExport).toBe(config);
23
- expect(typeof config).toBe('object');
24
- expect(Array.isArray(config.extends)).toBe(true);
25
- expect(config.extends).toContain('@commitlint/config-conventional');
18
+ expect(config).toBeTypeOf('object');
19
+ expect(config.extends).toEqual(['@commitlint/config-conventional']);
26
20
  expect(config.parserPreset).toBe(
27
21
  'conventional-changelog-conventionalcommits'
28
22
  );
@@ -30,18 +24,41 @@ describe('commitlint-config-spellbookx entrypoint', () => {
30
24
  expect(config.prompt).toBe(prompt);
31
25
  });
32
26
 
33
- test('should contain valid package metadata in helpers', () => {
27
+ it('exposes accurate package metadata', () => {
34
28
  expect(packageJson.name).toBe('commitlint-config-spellbookx');
35
- expect(packageJson.version).toBeDefined();
29
+ expect(packageJson.version).toMatch(/^\d+\.\d+\.\d+/);
30
+ });
31
+
32
+ it('exports all 11 standard conventional commit types', () => {
33
+ expect(COMMIT_TYPES).toEqual([
34
+ 'feat',
35
+ 'fix',
36
+ 'docs',
37
+ 'style',
38
+ 'refactor',
39
+ 'perf',
40
+ 'test',
41
+ 'chore',
42
+ 'ci',
43
+ 'build',
44
+ 'revert',
45
+ ]);
46
+ });
47
+
48
+ it('configures cz-git interactive prompt questions and settings', () => {
49
+ expect(prompt?.settings?.enableMultipleScopes).toBe(true);
50
+ expect(prompt?.settings?.scopeEnumSeparator).toBe(',');
51
+ expect(prompt?.questions?.scope?.enum).toBe(scopesEnum);
52
+
53
+ for (const type of COMMIT_TYPES) {
54
+ expect(prompt?.questions?.type?.enum?.[type]).toBeDefined();
55
+ }
36
56
  });
37
57
 
38
- test('should export conventional commit types and scope constants', () => {
39
- expect(Array.isArray(COMMIT_TYPES)).toBe(true);
40
- expect(COMMIT_TYPES).toContain('feat');
41
- expect(COMMIT_TYPES).toContain('fix');
58
+ it('exports special scope constants and dynamically discovered scopes', () => {
42
59
  expect(SCOPE_WORKSPACE).toBe('workspace');
43
60
  expect(SCOPE_RELEASE).toBe('release');
44
61
  expect(Array.isArray(scopesArray)).toBe(true);
45
- expect(typeof scopesEnum).toBe('object');
62
+ expect(scopesEnum).toBeTypeOf('object');
46
63
  });
47
64
  });
@@ -1,65 +1,37 @@
1
- /**
2
- * @file lint.test.ts
3
- * Tests verifying conventional commit validation rules using `@commitlint/lint`.
4
- */
5
-
6
- import { describe, expect, test } from 'vitest';
1
+ import { describe, expect, it } from 'vitest';
7
2
 
8
3
  import lint from '@commitlint/lint';
9
4
 
10
5
  import config from '../src/index.js';
11
6
 
12
- describe('commitlint validation rules', () => {
13
- test('should validate valid conventional commits', async () => {
14
- const result = await lint(
15
- 'feat(eslint-plugin-spellbookx): add new awesome feature',
16
- config.rules
17
- );
18
- expect(result.valid).toBe(true);
19
- expect(result.errors).toHaveLength(0);
20
- });
21
-
22
- test('should validate valid workspace scope commits', async () => {
23
- const result = await lint(
24
- 'chore(workspace): update root configs',
25
- config.rules
26
- );
27
- expect(result.valid).toBe(true);
28
- expect(result.errors).toHaveLength(0);
29
- });
7
+ describe('commitlint-config-spellbookx (lint rules)', () => {
8
+ it.each([
9
+ 'feat(eslint-plugin-spellbookx): add new flat config preset',
10
+ 'fix(prettier-config-spellbookx): resolve plugin sort order',
11
+ 'chore(workspace): update root dependencies',
12
+ 'chore(release): bump to v1.1.1',
13
+ 'docs(@spellbookx/recipes): document ignore globs',
14
+ 'test(spellbookx): rewrite test suite with vitest',
15
+ ])('accepts valid conventional commit: "%s"', async (message) => {
16
+ const result = await lint(message, config.rules);
30
17
 
31
- test('should validate valid release scope commits', async () => {
32
- const result = await lint('feat(release): publish v1.0.0', config.rules);
33
18
  expect(result.valid).toBe(true);
34
19
  expect(result.errors).toHaveLength(0);
35
20
  });
36
21
 
37
- test('should fail when type is invalid', async () => {
38
- const result = await lint('unknown(workspace): some change', config.rules);
39
- expect(result.valid).toBe(false);
40
- expect(result.errors.some((e) => e.name === 'type-enum')).toBe(true);
41
- });
42
-
43
- test('should fail when scope is not in scope-enum', async () => {
44
- const result = await lint(
45
- 'feat(nonexistent-scope): some change',
46
- config.rules
47
- );
48
- expect(result.valid).toBe(false);
49
- expect(result.errors.some((e) => e.name === 'scope-enum')).toBe(true);
50
- });
51
-
52
- test('should fail when subject has a trailing period', async () => {
53
- const result = await lint('fix(workspace): fix config typo.', config.rules);
54
- expect(result.valid).toBe(false);
55
- expect(result.errors.some((e) => e.name === 'subject-full-stop')).toBe(
56
- true
57
- );
58
- });
59
-
60
- test('should fail when subject is empty', async () => {
61
- const result = await lint('fix(workspace): ', config.rules);
62
- expect(result.valid).toBe(false);
63
- expect(result.errors.some((e) => e.name === 'subject-empty')).toBe(true);
64
- });
22
+ it.each([
23
+ ['unknown(workspace): invalid commit type', 'type-enum'],
24
+ ['feat(nonexistent-scope): unknown package scope', 'scope-enum'],
25
+ ['fix(workspace): trailing period in subject.', 'subject-full-stop'],
26
+ ['fix(workspace): ', 'subject-empty'],
27
+ [`feat(workspace): ${'a'.repeat(80)}`, 'header-max-length'],
28
+ ])(
29
+ 'rejects invalid commit "%s" with rule "%s"',
30
+ async (message, expectedRule) => {
31
+ const result = await lint(message, config.rules);
32
+
33
+ expect(result.valid).toBe(false);
34
+ expect(result.errors.some((err) => err.name === expectedRule)).toBe(true);
35
+ }
36
+ );
65
37
  });
@@ -1,9 +1,4 @@
1
- /**
2
- * @file scopes.test.ts
3
- * Tests for scope identifiers, scope descriptions, and scopes enumeration.
4
- */
5
-
6
- import { describe, expect, test } from 'vitest';
1
+ import { describe, expect, it } from 'vitest';
7
2
 
8
3
  import {
9
4
  getScopeDescription,
@@ -13,43 +8,35 @@ import {
13
8
  scopesEnum,
14
9
  } from '../src/index.js';
15
10
 
16
- describe('commitlint scopes', () => {
17
- test('constants should be properly defined', () => {
11
+ describe('commitlint-config-spellbookx (scopes)', () => {
12
+ it('defines workspace and release scope constants', () => {
18
13
  expect(SCOPE_WORKSPACE).toBe('workspace');
19
14
  expect(SCOPE_RELEASE).toBe('release');
20
15
  });
21
16
 
22
- test('scopesArray should contain workspace, release, and monorepo packages', () => {
23
- expect(Array.isArray(scopesArray)).toBe(true);
24
- expect(scopesArray).toContain(SCOPE_WORKSPACE);
25
- expect(scopesArray).toContain(SCOPE_RELEASE);
26
- expect(scopesArray).toContain('commitlint-config-spellbookx');
27
- expect(scopesArray).toContain('cspell-config-spellbookx');
28
- expect(scopesArray).toContain('eslint-plugin-spellbookx');
29
- expect(scopesArray).toContain('prettier-config-spellbookx');
30
- expect(scopesArray).toContain('@spellbookx/recipes');
17
+ it.each([
18
+ SCOPE_WORKSPACE,
19
+ SCOPE_RELEASE,
20
+ '@spellbookx/recipes',
21
+ 'commitlint-config-spellbookx',
22
+ 'cspell-config-spellbookx',
23
+ 'eslint-plugin-spellbookx',
24
+ 'prettier-config-spellbookx',
25
+ 'spellbookx',
26
+ ])('includes "%s" in scopesArray and scopesEnum', (scope) => {
27
+ expect(scopesArray).toContain(scope);
28
+ expect(scopesEnum[scope]).toEqual({
29
+ description: getScopeDescription(scope),
30
+ });
31
31
  });
32
32
 
33
- test('getScopeDescription should return user-friendly descriptions', () => {
33
+ it('returns descriptive text for special and package scopes', () => {
34
34
  expect(getScopeDescription(SCOPE_WORKSPACE)).toBe(
35
35
  'workspace scope - root files such as config files, root package.json, etc'
36
36
  );
37
37
  expect(getScopeDescription(SCOPE_RELEASE)).toBe(
38
38
  'Special scope for release commits'
39
39
  );
40
- expect(getScopeDescription('custom-pkg')).toBe('custom-pkg scope');
41
- });
42
-
43
- test('scopesEnum should populate descriptions for all scopes', () => {
44
- expect(scopesEnum[SCOPE_WORKSPACE]?.description).toBe(
45
- getScopeDescription(SCOPE_WORKSPACE)
46
- );
47
- expect(scopesEnum[SCOPE_RELEASE]?.description).toBe(
48
- getScopeDescription(SCOPE_RELEASE)
49
- );
50
- for (const scope of scopesArray) {
51
- expect(scopesEnum[scope]).toBeDefined();
52
- expect(scopesEnum[scope]?.description).toBe(getScopeDescription(scope));
53
- }
40
+ expect(getScopeDescription('my-custom-pkg')).toBe('my-custom-pkg scope');
54
41
  });
55
42
  });
@@ -1,13 +1,15 @@
1
- /**
2
- * @file workspaces.test.ts
3
- * Tests for git root discovery, workspace config parsing, and scopes gathering.
4
- */
5
-
6
- import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ mkdtempSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from 'node:fs';
8
+ import os from 'node:os';
7
9
  import path from 'node:path';
8
10
  import { fileURLToPath } from 'node:url';
9
11
 
10
- import { afterAll, beforeAll, describe, expect, test } from 'vitest';
12
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
11
13
 
12
14
  import {
13
15
  findGitRoot,
@@ -16,61 +18,120 @@ import {
16
18
  } from '../src/lib/index.js';
17
19
 
18
20
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
- const testDir = path.resolve(__dirname, '../temp-test-commitlint');
20
21
 
21
- describe('workspace discovery and parsing', () => {
22
- beforeAll(() => {
23
- if (existsSync(testDir)) rmSync(testDir, { recursive: true, force: true });
24
- mkdirSync(testDir, { recursive: true });
25
- });
22
+ describe('commitlint-config-spellbookx (workspace discovery)', () => {
23
+ let tempDir: string;
26
24
 
27
- afterAll(() => {
28
- if (existsSync(testDir)) rmSync(testDir, { recursive: true, force: true });
25
+ beforeEach(() => {
26
+ tempDir = mkdtempSync(path.join(os.tmpdir(), 'sbx-commitlint-'));
29
27
  });
30
28
 
31
- test('findGitRoot should locate git root directory', () => {
32
- const root = findGitRoot(__dirname);
33
- expect(root).not.toBeNull();
34
- expect(existsSync(path.join(root!, '.git'))).toBe(true);
29
+ afterEach(() => {
30
+ rmSync(tempDir, { recursive: true, force: true });
35
31
  });
36
32
 
37
- test('should parse workspaces from JSON correctly', () => {
38
- const jsonPath = path.join(testDir, 'package.json');
39
- mkdirSync(testDir, { recursive: true });
40
- writeFileSync(
41
- jsonPath,
42
- JSON.stringify({ workspaces: ['packages/*', 'apps/*'] })
43
- );
33
+ describe('findGitRoot', () => {
34
+ it('locates the git root directory from a nested package path', () => {
35
+ const root = findGitRoot(__dirname);
44
36
 
45
- const workspaces = getWorkspacesRawPath(jsonPath, 'json', 'workspaces');
46
- expect(workspaces).toEqual(['packages/*', 'apps/*']);
47
- });
37
+ expect(root).not.toBeNull();
38
+ expect(existsSync(path.join(root!, '.git'))).toBe(true);
39
+ });
48
40
 
49
- test('should parse packages from YAML correctly', () => {
50
- const yamlPath = path.join(testDir, 'pnpm-workspace.yaml');
51
- mkdirSync(testDir, { recursive: true });
52
- writeFileSync(yamlPath, 'packages:\n - "packages/*"\n - "plugins/*"\n');
41
+ it('returns null when no .git directory exists up to filesystem root', () => {
42
+ const root = findGitRoot(tempDir);
53
43
 
54
- const packages = getWorkspacesRawPath(yamlPath, 'yaml', 'packages');
55
- expect(packages).toEqual(['packages/*', 'plugins/*']);
44
+ expect(root).toBeNull();
45
+ });
56
46
  });
57
47
 
58
- test('should throw on unsupported parser', () => {
59
- const filePath = path.join(testDir, 'config.txt');
60
- writeFileSync(filePath, 'some text');
61
- expect(() => {
62
- // @ts-expect-error Testing invalid parser
63
- getWorkspacesRawPath(filePath, 'xml');
64
- }).toThrow(/Unsupported parser type/);
48
+ describe('getWorkspacesRawPath', () => {
49
+ it('parses workspace globs from a JSON file', () => {
50
+ const jsonPath = path.join(tempDir, 'package.json');
51
+ writeFileSync(
52
+ jsonPath,
53
+ JSON.stringify({ workspaces: ['packages/*', 'apps/*'] })
54
+ );
55
+
56
+ expect(getWorkspacesRawPath(jsonPath, 'json', 'workspaces')).toEqual([
57
+ 'packages/*',
58
+ 'apps/*',
59
+ ]);
60
+ });
61
+
62
+ it('parses package globs from a YAML file', () => {
63
+ const yamlPath = path.join(tempDir, 'pnpm-workspace.yaml');
64
+ writeFileSync(yamlPath, 'packages:\n - "packages/*"\n - "plugins/*"\n');
65
+
66
+ expect(getWorkspacesRawPath(yamlPath, 'yaml', 'packages')).toEqual([
67
+ 'packages/*',
68
+ 'plugins/*',
69
+ ]);
70
+ });
71
+
72
+ it('returns an empty array when file does not exist or property is missing', () => {
73
+ expect(
74
+ getWorkspacesRawPath(path.join(tempDir, 'missing.json'), 'json')
75
+ ).toEqual([]);
76
+
77
+ const jsonPath = path.join(tempDir, 'empty.json');
78
+ writeFileSync(jsonPath, JSON.stringify({ name: 'no-workspaces' }));
79
+ expect(getWorkspacesRawPath(jsonPath, 'json', 'workspaces')).toEqual([]);
80
+ });
81
+
82
+ it('throws an error when given an unsupported parser type', () => {
83
+ const filePath = path.join(tempDir, 'config.txt');
84
+ writeFileSync(filePath, 'content');
85
+
86
+ expect(() => {
87
+ // @ts-expect-error Testing invalid parser runtime check
88
+ getWorkspacesRawPath(filePath, 'xml');
89
+ }).toThrow(/Unsupported parser type: xml/);
90
+ });
65
91
  });
66
92
 
67
- test('should return scopes including workspace and monorepo packages', () => {
68
- const scopes = getScopes();
69
- expect(Array.isArray(scopes)).toBe(true);
70
- expect(scopes).toContain('commitlint-config-spellbookx');
71
- expect(scopes).toContain('cspell-config-spellbookx');
72
- expect(scopes).toContain('eslint-plugin-spellbookx');
73
- expect(scopes).toContain('prettier-config-spellbookx');
74
- expect(scopes).toContain('@spellbookx/recipes');
93
+ describe('getScopes', () => {
94
+ it('discovers all package scopes in the current monorepo', () => {
95
+ const scopes = getScopes(__dirname);
96
+
97
+ expect(scopes).toEqual(
98
+ expect.arrayContaining([
99
+ '@spellbookx/recipes',
100
+ 'commitlint-config-spellbookx',
101
+ 'cspell-config-spellbookx',
102
+ 'eslint-plugin-spellbookx',
103
+ 'prettier-config-spellbookx',
104
+ 'spellbookx',
105
+ ])
106
+ );
107
+ });
108
+
109
+ it('discovers scopes from a simulated git repository with workspaces and ignores malformed package.json', () => {
110
+ mkdirSync(path.join(tempDir, '.git'));
111
+ writeFileSync(
112
+ path.join(tempDir, 'package.json'),
113
+ JSON.stringify({ name: 'root-pkg', workspaces: ['packages/*'] })
114
+ );
115
+
116
+ const pkgA = path.join(tempDir, 'packages/pkg-a');
117
+ const pkgInvalid = path.join(tempDir, 'packages/pkg-invalid');
118
+ mkdirSync(pkgA, { recursive: true });
119
+ mkdirSync(pkgInvalid, { recursive: true });
120
+
121
+ writeFileSync(
122
+ path.join(pkgA, 'package.json'),
123
+ JSON.stringify({ name: '@acme/pkg-a' })
124
+ );
125
+ writeFileSync(path.join(pkgInvalid, 'package.json'), '{ invalid json');
126
+
127
+ const scopes = getScopes(tempDir);
128
+
129
+ expect(scopes).toContain('root-pkg');
130
+ expect(scopes).toContain('@acme/pkg-a');
131
+ });
132
+
133
+ it('throws a fatal error when invoked outside a git repository', () => {
134
+ expect(() => getScopes(tempDir)).toThrow('[FATAL] Not a repository.');
135
+ });
75
136
  });
76
137
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commitlint-config-spellbookx",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "private": false,
5
5
  "description": "A shareable Commitlint configuration for enforcing conventional commits.",
6
6
  "keywords": [
@@ -90,6 +90,6 @@
90
90
  "scripts": {
91
91
  "build": "rolldown -c && tsc",
92
92
  "release": "pnpm publish --access public --no-git-checks",
93
- "test": "vitest run"
93
+ "test": "vitest run --no-fsModuleCache"
94
94
  }
95
95
  }