@grunnverk/kilde 0.1.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/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
- package/.github/pull_request_template.md +48 -0
- package/.github/workflows/deploy-docs.yml +59 -0
- package/.github/workflows/npm-publish.yml +48 -0
- package/.github/workflows/test.yml +48 -0
- package/CHANGELOG.md +92 -0
- package/CONTRIBUTING.md +438 -0
- package/LICENSE +190 -0
- package/PROJECT_SUMMARY.md +318 -0
- package/README.md +444 -0
- package/RELEASE_CHECKLIST.md +182 -0
- package/dist/application.js +166 -0
- package/dist/application.js.map +1 -0
- package/dist/commands/release.js +326 -0
- package/dist/commands/release.js.map +1 -0
- package/dist/constants.js +122 -0
- package/dist/constants.js.map +1 -0
- package/dist/logging.js +176 -0
- package/dist/logging.js.map +1 -0
- package/dist/main.js +24 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp-server.js +17467 -0
- package/dist/mcp-server.js.map +7 -0
- package/dist/utils/config.js +89 -0
- package/dist/utils/config.js.map +1 -0
- package/docs/AI_GUIDE.md +618 -0
- package/eslint.config.mjs +85 -0
- package/guide/architecture.md +776 -0
- package/guide/commands.md +580 -0
- package/guide/configuration.md +779 -0
- package/guide/mcp-integration.md +708 -0
- package/guide/overview.md +225 -0
- package/package.json +91 -0
- package/scripts/build-mcp.js +115 -0
- package/scripts/test-mcp-compliance.js +254 -0
- package/src/application.ts +246 -0
- package/src/commands/release.ts +450 -0
- package/src/constants.ts +162 -0
- package/src/logging.ts +210 -0
- package/src/main.ts +25 -0
- package/src/mcp/prompts/index.ts +98 -0
- package/src/mcp/resources.ts +121 -0
- package/src/mcp/server.ts +195 -0
- package/src/mcp/tools.ts +219 -0
- package/src/types.ts +131 -0
- package/src/utils/config.ts +181 -0
- package/tests/application.test.ts +114 -0
- package/tests/commands/commit.test.ts +248 -0
- package/tests/commands/release.test.ts +325 -0
- package/tests/constants.test.ts +118 -0
- package/tests/logging.test.ts +142 -0
- package/tests/mcp/prompts/index.test.ts +202 -0
- package/tests/mcp/resources.test.ts +166 -0
- package/tests/mcp/tools.test.ts +211 -0
- package/tests/utils/config.test.ts +212 -0
- package/tsconfig.json +32 -0
- package/vite.config.ts +107 -0
- package/vitest.config.ts +40 -0
- package/website/index.html +14 -0
- package/website/src/App.css +142 -0
- package/website/src/App.tsx +34 -0
- package/website/src/components/Commands.tsx +182 -0
- package/website/src/components/Configuration.tsx +214 -0
- package/website/src/components/Examples.tsx +234 -0
- package/website/src/components/Footer.css +99 -0
- package/website/src/components/Footer.tsx +93 -0
- package/website/src/components/GettingStarted.tsx +94 -0
- package/website/src/components/Hero.css +95 -0
- package/website/src/components/Hero.tsx +50 -0
- package/website/src/components/Navigation.css +102 -0
- package/website/src/components/Navigation.tsx +57 -0
- package/website/src/index.css +36 -0
- package/website/src/main.tsx +10 -0
- package/website/vite.config.ts +12 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import { getLogger } from '../logging';
|
|
5
|
+
import { Config } from '@grunnverk/core';
|
|
6
|
+
import { KILDE_DEFAULTS } from '../constants';
|
|
7
|
+
|
|
8
|
+
const CONFIG_FILES = [
|
|
9
|
+
'.kilde/config.yaml',
|
|
10
|
+
'.kilde/config.yml',
|
|
11
|
+
'.kilderc.yaml',
|
|
12
|
+
'.kilderc.yml',
|
|
13
|
+
'.kilderc.json',
|
|
14
|
+
'kilde.config.json',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load configuration from file
|
|
19
|
+
*/
|
|
20
|
+
export async function loadConfig(cwd: string = process.cwd()): Promise<Config | null> {
|
|
21
|
+
const logger = getLogger();
|
|
22
|
+
|
|
23
|
+
for (const filename of CONFIG_FILES) {
|
|
24
|
+
const configPath = path.join(cwd, filename);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
28
|
+
|
|
29
|
+
let config: any;
|
|
30
|
+
if (filename.endsWith('.json')) {
|
|
31
|
+
config = JSON.parse(content);
|
|
32
|
+
} else if (filename.endsWith('.yaml') || filename.endsWith('.yml')) {
|
|
33
|
+
config = yaml.load(content);
|
|
34
|
+
} else {
|
|
35
|
+
// Try JSON first, then YAML
|
|
36
|
+
try {
|
|
37
|
+
config = JSON.parse(content);
|
|
38
|
+
} catch {
|
|
39
|
+
config = yaml.load(content);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Return config as-is
|
|
44
|
+
logger.verbose(`Loaded configuration from ${configPath}`);
|
|
45
|
+
return config as Config;
|
|
46
|
+
} catch (error: any) {
|
|
47
|
+
if (error.code !== 'ENOENT') {
|
|
48
|
+
logger.warn(`CONFIG_LOAD_FAILED: Failed to load configuration file | Path: ${configPath} | Error: ${error.message} | Action: Using defaults`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
logger.verbose('No configuration file found, using defaults');
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get default configuration
|
|
59
|
+
*/
|
|
60
|
+
export function getDefaultConfig(): Config {
|
|
61
|
+
return KILDE_DEFAULTS;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Deep merge two objects
|
|
66
|
+
*/
|
|
67
|
+
function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
|
|
68
|
+
const result = { ...target };
|
|
69
|
+
|
|
70
|
+
for (const key in source) {
|
|
71
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
72
|
+
const sourceValue = source[key];
|
|
73
|
+
const targetValue = result[key];
|
|
74
|
+
|
|
75
|
+
if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue) &&
|
|
76
|
+
targetValue && typeof targetValue === 'object' && !Array.isArray(targetValue)) {
|
|
77
|
+
result[key] = deepMerge(targetValue, sourceValue) as any;
|
|
78
|
+
} else if (sourceValue !== undefined) {
|
|
79
|
+
result[key] = sourceValue as any;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Merge loaded config with defaults
|
|
89
|
+
*/
|
|
90
|
+
export function mergeWithDefaults(config: Config | null): Config {
|
|
91
|
+
const defaults = getDefaultConfig();
|
|
92
|
+
|
|
93
|
+
if (!config) {
|
|
94
|
+
return defaults;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return deepMerge(defaults, config);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get effective configuration (loaded + defaults)
|
|
102
|
+
*/
|
|
103
|
+
export async function getEffectiveConfig(cwd: string = process.cwd()): Promise<Config> {
|
|
104
|
+
const loaded = await loadConfig(cwd);
|
|
105
|
+
return mergeWithDefaults(loaded);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create a sample configuration file
|
|
110
|
+
*/
|
|
111
|
+
export function createSampleConfig(): string {
|
|
112
|
+
const config = {
|
|
113
|
+
// Global settings
|
|
114
|
+
verbose: false,
|
|
115
|
+
debug: false,
|
|
116
|
+
model: 'gpt-4o-mini',
|
|
117
|
+
openaiReasoning: 'low',
|
|
118
|
+
outputDirectory: 'output/kilde',
|
|
119
|
+
|
|
120
|
+
// Commit configuration
|
|
121
|
+
commit: {
|
|
122
|
+
sendit: false,
|
|
123
|
+
interactive: false,
|
|
124
|
+
messageLimit: 3,
|
|
125
|
+
maxDiffBytes: 20480,
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// Release configuration
|
|
129
|
+
release: {
|
|
130
|
+
interactive: false,
|
|
131
|
+
messageLimit: 3,
|
|
132
|
+
maxDiffBytes: 20480,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return yaml.dump(config, {
|
|
137
|
+
indent: 2,
|
|
138
|
+
lineWidth: 100,
|
|
139
|
+
noRefs: true,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Save sample configuration to file
|
|
145
|
+
*/
|
|
146
|
+
export async function saveSampleConfig(cwd: string = process.cwd()): Promise<string> {
|
|
147
|
+
const logger = getLogger();
|
|
148
|
+
const configDir = path.join(cwd, '.kilde');
|
|
149
|
+
const configPath = path.join(configDir, 'config.yaml');
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
// Create directory if it doesn't exist
|
|
153
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
154
|
+
|
|
155
|
+
// Write sample config
|
|
156
|
+
const sampleConfig = createSampleConfig();
|
|
157
|
+
await fs.writeFile(configPath, sampleConfig, 'utf-8');
|
|
158
|
+
|
|
159
|
+
logger.info(`Created sample configuration file at ${configPath}`);
|
|
160
|
+
return configPath;
|
|
161
|
+
} catch (error: any) {
|
|
162
|
+
logger.error(`Failed to create sample configuration: ${error.message}`);
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Check if configuration file exists
|
|
169
|
+
*/
|
|
170
|
+
export async function configFileExists(cwd: string = process.cwd()): Promise<boolean> {
|
|
171
|
+
for (const filename of CONFIG_FILES) {
|
|
172
|
+
const configPath = path.join(cwd, filename);
|
|
173
|
+
try {
|
|
174
|
+
await fs.access(configPath);
|
|
175
|
+
return true;
|
|
176
|
+
} catch {
|
|
177
|
+
// File doesn't exist, continue checking
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { getVersionInfo, configureEarlyLogging } from '../src/application';
|
|
3
|
+
import { getLogger, setLogLevel } from '../src/logging';
|
|
4
|
+
import { VERSION, BUILD_HOSTNAME, BUILD_TIMESTAMP } from '../src/constants';
|
|
5
|
+
|
|
6
|
+
describe('application', () => {
|
|
7
|
+
let originalArgv: string[];
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
originalArgv = [...process.argv];
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
process.argv = originalArgv;
|
|
15
|
+
setLogLevel('info');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('getVersionInfo', () => {
|
|
19
|
+
it('should return version information', () => {
|
|
20
|
+
const info = getVersionInfo();
|
|
21
|
+
|
|
22
|
+
expect(info.version).toBe(VERSION);
|
|
23
|
+
expect(info.buildHostname).toBe(BUILD_HOSTNAME);
|
|
24
|
+
expect(info.buildTimestamp).toBe(BUILD_TIMESTAMP);
|
|
25
|
+
expect(info.formatted).toContain(VERSION);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should include build metadata in formatted string', () => {
|
|
29
|
+
const info = getVersionInfo();
|
|
30
|
+
|
|
31
|
+
expect(info.formatted).toContain('Built on:');
|
|
32
|
+
expect(info.formatted).toContain('Build time:');
|
|
33
|
+
expect(info.formatted).toContain(BUILD_HOSTNAME);
|
|
34
|
+
expect(info.formatted).toContain(BUILD_TIMESTAMP);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should have all required fields', () => {
|
|
38
|
+
const info = getVersionInfo();
|
|
39
|
+
|
|
40
|
+
expect(info).toHaveProperty('version');
|
|
41
|
+
expect(info).toHaveProperty('buildHostname');
|
|
42
|
+
expect(info).toHaveProperty('buildTimestamp');
|
|
43
|
+
expect(info).toHaveProperty('formatted');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should return string types for all fields', () => {
|
|
47
|
+
const info = getVersionInfo();
|
|
48
|
+
|
|
49
|
+
expect(typeof info.version).toBe('string');
|
|
50
|
+
expect(typeof info.buildHostname).toBe('string');
|
|
51
|
+
expect(typeof info.buildTimestamp).toBe('string');
|
|
52
|
+
expect(typeof info.formatted).toBe('string');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('configureEarlyLogging', () => {
|
|
57
|
+
it('should set debug log level when --debug flag is present', () => {
|
|
58
|
+
process.argv = ['node', 'kilde', '--debug'];
|
|
59
|
+
configureEarlyLogging();
|
|
60
|
+
|
|
61
|
+
const logger = getLogger() as any;
|
|
62
|
+
expect(logger.level).toBe('debug');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should set debug log level when -d flag is present', () => {
|
|
66
|
+
process.argv = ['node', 'kilde', '-d'];
|
|
67
|
+
configureEarlyLogging();
|
|
68
|
+
|
|
69
|
+
const logger = getLogger() as any;
|
|
70
|
+
expect(logger.level).toBe('debug');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should set verbose log level when --verbose flag is present', () => {
|
|
74
|
+
process.argv = ['node', 'kilde', '--verbose'];
|
|
75
|
+
configureEarlyLogging();
|
|
76
|
+
|
|
77
|
+
const logger = getLogger() as any;
|
|
78
|
+
expect(logger.level).toBe('verbose');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should set verbose log level when -v flag is present', () => {
|
|
82
|
+
process.argv = ['node', 'kilde', '-v'];
|
|
83
|
+
configureEarlyLogging();
|
|
84
|
+
|
|
85
|
+
const logger = getLogger() as any;
|
|
86
|
+
expect(logger.level).toBe('verbose');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should prioritize debug over verbose when both are present', () => {
|
|
90
|
+
process.argv = ['node', 'kilde', '--verbose', '--debug'];
|
|
91
|
+
configureEarlyLogging();
|
|
92
|
+
|
|
93
|
+
const logger = getLogger() as any;
|
|
94
|
+
expect(logger.level).toBe('debug');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should not change log level when no flags are present', () => {
|
|
98
|
+
setLogLevel('info');
|
|
99
|
+
process.argv = ['node', 'kilde'];
|
|
100
|
+
configureEarlyLogging();
|
|
101
|
+
|
|
102
|
+
const logger = getLogger() as any;
|
|
103
|
+
expect(logger.level).toBe('info');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle flags in any position', () => {
|
|
107
|
+
process.argv = ['node', 'kilde', 'commit', '--debug', '--sendit'];
|
|
108
|
+
configureEarlyLogging();
|
|
109
|
+
|
|
110
|
+
const logger = getLogger() as any;
|
|
111
|
+
expect(logger.level).toBe('debug');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import * as CommandsGit from '@grunnverk/commands-git';
|
|
3
|
+
import { Config } from '@grunnverk/core';
|
|
4
|
+
|
|
5
|
+
// Mock the commands-git module
|
|
6
|
+
vi.mock('@grunnverk/commands-git', () => ({
|
|
7
|
+
commit: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
describe('commit command integration', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vi.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('basic commit functionality', () => {
|
|
16
|
+
it('should call commands-git commit with proper config', async () => {
|
|
17
|
+
const mockConfig: Config = {
|
|
18
|
+
configDirectory: '/test/.kilde',
|
|
19
|
+
discoveredConfigDirs: [],
|
|
20
|
+
resolvedConfigDirs: [],
|
|
21
|
+
verbose: false,
|
|
22
|
+
debug: false,
|
|
23
|
+
dryRun: false,
|
|
24
|
+
commit: {
|
|
25
|
+
add: true,
|
|
26
|
+
sendit: true,
|
|
27
|
+
cached: false,
|
|
28
|
+
interactive: false,
|
|
29
|
+
amend: false,
|
|
30
|
+
},
|
|
31
|
+
} as Config;
|
|
32
|
+
|
|
33
|
+
vi.mocked(CommandsGit.commit).mockResolvedValue('Commit created successfully');
|
|
34
|
+
|
|
35
|
+
const result = await CommandsGit.commit(mockConfig);
|
|
36
|
+
|
|
37
|
+
expect(CommandsGit.commit).toHaveBeenCalledWith(mockConfig);
|
|
38
|
+
expect(result).toBe('Commit created successfully');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should handle commit with add flag', async () => {
|
|
42
|
+
const mockConfig: Config = {
|
|
43
|
+
configDirectory: '/test/.kilde',
|
|
44
|
+
discoveredConfigDirs: [],
|
|
45
|
+
resolvedConfigDirs: [],
|
|
46
|
+
commit: {
|
|
47
|
+
add: true,
|
|
48
|
+
},
|
|
49
|
+
} as Config;
|
|
50
|
+
|
|
51
|
+
vi.mocked(CommandsGit.commit).mockResolvedValue('Changes staged and committed');
|
|
52
|
+
|
|
53
|
+
const result = await CommandsGit.commit(mockConfig);
|
|
54
|
+
|
|
55
|
+
expect(result).toBe('Changes staged and committed');
|
|
56
|
+
expect(CommandsGit.commit).toHaveBeenCalledWith(
|
|
57
|
+
expect.objectContaining({
|
|
58
|
+
commit: expect.objectContaining({
|
|
59
|
+
add: true,
|
|
60
|
+
}),
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle commit with sendit flag', async () => {
|
|
66
|
+
const mockConfig: Config = {
|
|
67
|
+
configDirectory: '/test/.kilde',
|
|
68
|
+
discoveredConfigDirs: [],
|
|
69
|
+
resolvedConfigDirs: [],
|
|
70
|
+
commit: {
|
|
71
|
+
sendit: true,
|
|
72
|
+
},
|
|
73
|
+
} as Config;
|
|
74
|
+
|
|
75
|
+
vi.mocked(CommandsGit.commit).mockResolvedValue('Commit created: abc1234');
|
|
76
|
+
|
|
77
|
+
const result = await CommandsGit.commit(mockConfig);
|
|
78
|
+
|
|
79
|
+
expect(result).toContain('Commit created');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('commit with context', () => {
|
|
84
|
+
it('should handle commit with context string', async () => {
|
|
85
|
+
const mockConfig: Config = {
|
|
86
|
+
configDirectory: '/test/.kilde',
|
|
87
|
+
discoveredConfigDirs: [],
|
|
88
|
+
resolvedConfigDirs: [],
|
|
89
|
+
commit: {
|
|
90
|
+
context: 'Fix authentication bug',
|
|
91
|
+
},
|
|
92
|
+
} as Config;
|
|
93
|
+
|
|
94
|
+
vi.mocked(CommandsGit.commit).mockResolvedValue('Commit with context created');
|
|
95
|
+
|
|
96
|
+
await CommandsGit.commit(mockConfig);
|
|
97
|
+
|
|
98
|
+
expect(CommandsGit.commit).toHaveBeenCalledWith(
|
|
99
|
+
expect.objectContaining({
|
|
100
|
+
commit: expect.objectContaining({
|
|
101
|
+
context: 'Fix authentication bug',
|
|
102
|
+
}),
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should handle commit with context files', async () => {
|
|
108
|
+
const mockConfig: Config = {
|
|
109
|
+
configDirectory: '/test/.kilde',
|
|
110
|
+
discoveredConfigDirs: [],
|
|
111
|
+
resolvedConfigDirs: [],
|
|
112
|
+
commit: {
|
|
113
|
+
contextFiles: ['CHANGELOG.md', 'README.md'],
|
|
114
|
+
},
|
|
115
|
+
} as Config;
|
|
116
|
+
|
|
117
|
+
vi.mocked(CommandsGit.commit).mockResolvedValue('Commit with context files created');
|
|
118
|
+
|
|
119
|
+
await CommandsGit.commit(mockConfig);
|
|
120
|
+
|
|
121
|
+
expect(CommandsGit.commit).toHaveBeenCalledWith(
|
|
122
|
+
expect.objectContaining({
|
|
123
|
+
commit: expect.objectContaining({
|
|
124
|
+
contextFiles: ['CHANGELOG.md', 'README.md'],
|
|
125
|
+
}),
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('commit modes', () => {
|
|
132
|
+
it('should handle interactive mode', async () => {
|
|
133
|
+
const mockConfig: Config = {
|
|
134
|
+
configDirectory: '/test/.kilde',
|
|
135
|
+
discoveredConfigDirs: [],
|
|
136
|
+
resolvedConfigDirs: [],
|
|
137
|
+
commit: {
|
|
138
|
+
interactive: true,
|
|
139
|
+
},
|
|
140
|
+
} as Config;
|
|
141
|
+
|
|
142
|
+
vi.mocked(CommandsGit.commit).mockResolvedValue('Interactive commit completed');
|
|
143
|
+
|
|
144
|
+
await CommandsGit.commit(mockConfig);
|
|
145
|
+
|
|
146
|
+
expect(CommandsGit.commit).toHaveBeenCalledWith(
|
|
147
|
+
expect.objectContaining({
|
|
148
|
+
commit: expect.objectContaining({
|
|
149
|
+
interactive: true,
|
|
150
|
+
}),
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should handle amend mode', async () => {
|
|
156
|
+
const mockConfig: Config = {
|
|
157
|
+
configDirectory: '/test/.kilde',
|
|
158
|
+
discoveredConfigDirs: [],
|
|
159
|
+
resolvedConfigDirs: [],
|
|
160
|
+
commit: {
|
|
161
|
+
amend: true,
|
|
162
|
+
},
|
|
163
|
+
} as Config;
|
|
164
|
+
|
|
165
|
+
vi.mocked(CommandsGit.commit).mockResolvedValue('Commit amended');
|
|
166
|
+
|
|
167
|
+
await CommandsGit.commit(mockConfig);
|
|
168
|
+
|
|
169
|
+
expect(CommandsGit.commit).toHaveBeenCalledWith(
|
|
170
|
+
expect.objectContaining({
|
|
171
|
+
commit: expect.objectContaining({
|
|
172
|
+
amend: true,
|
|
173
|
+
}),
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should handle cached mode', async () => {
|
|
179
|
+
const mockConfig: Config = {
|
|
180
|
+
configDirectory: '/test/.kilde',
|
|
181
|
+
discoveredConfigDirs: [],
|
|
182
|
+
resolvedConfigDirs: [],
|
|
183
|
+
commit: {
|
|
184
|
+
cached: true,
|
|
185
|
+
},
|
|
186
|
+
} as Config;
|
|
187
|
+
|
|
188
|
+
vi.mocked(CommandsGit.commit).mockResolvedValue('Cached changes committed');
|
|
189
|
+
|
|
190
|
+
await CommandsGit.commit(mockConfig);
|
|
191
|
+
|
|
192
|
+
expect(CommandsGit.commit).toHaveBeenCalledWith(
|
|
193
|
+
expect.objectContaining({
|
|
194
|
+
commit: expect.objectContaining({
|
|
195
|
+
cached: true,
|
|
196
|
+
}),
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('dry-run mode', () => {
|
|
203
|
+
it('should handle dry-run mode', async () => {
|
|
204
|
+
const mockConfig: Config = {
|
|
205
|
+
configDirectory: '/test/.kilde',
|
|
206
|
+
discoveredConfigDirs: [],
|
|
207
|
+
resolvedConfigDirs: [],
|
|
208
|
+
dryRun: true,
|
|
209
|
+
commit: {
|
|
210
|
+
add: true,
|
|
211
|
+
},
|
|
212
|
+
} as Config;
|
|
213
|
+
|
|
214
|
+
vi.mocked(CommandsGit.commit).mockResolvedValue('[DRY-RUN] Would create commit');
|
|
215
|
+
|
|
216
|
+
const result = await CommandsGit.commit(mockConfig);
|
|
217
|
+
|
|
218
|
+
expect(result).toContain('DRY-RUN');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('error handling', () => {
|
|
223
|
+
it('should propagate errors from commit command', async () => {
|
|
224
|
+
const mockConfig: Config = {
|
|
225
|
+
configDirectory: '/test/.kilde',
|
|
226
|
+
discoveredConfigDirs: [],
|
|
227
|
+
resolvedConfigDirs: [],
|
|
228
|
+
} as Config;
|
|
229
|
+
|
|
230
|
+
const error = new Error('Git command failed');
|
|
231
|
+
vi.mocked(CommandsGit.commit).mockRejectedValue(error);
|
|
232
|
+
|
|
233
|
+
await expect(CommandsGit.commit(mockConfig)).rejects.toThrow('Git command failed');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should handle missing git repository error', async () => {
|
|
237
|
+
const mockConfig: Config = {
|
|
238
|
+
configDirectory: '/test/.kilde',
|
|
239
|
+
discoveredConfigDirs: [],
|
|
240
|
+
resolvedConfigDirs: [],
|
|
241
|
+
} as Config;
|
|
242
|
+
|
|
243
|
+
vi.mocked(CommandsGit.commit).mockRejectedValue(new Error('Not a git repository'));
|
|
244
|
+
|
|
245
|
+
await expect(CommandsGit.commit(mockConfig)).rejects.toThrow('Not a git repository');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|