@monodog/backend 1.2.6 → 1.2.8

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/dist/cli.js ADDED
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ /**
38
+ * CLI Entry Point for the Monorepo Analysis Engine.
39
+ * * This script is executed when a user runs the `monodog-cli` command
40
+ * in their project. It handles command-line arguments to determine
41
+ * whether to:
42
+ * 1. Start the API server for the dashboard.
43
+ * 2. Run a one-off analysis command. (Future functionality)
44
+ */
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const index_1 = require("./index"); // Assume index.ts exports this function
48
+ const config_loader_1 = require("./config-loader");
49
+ const appConfig = (0, config_loader_1.loadConfig)();
50
+ // --- Argument Parsing ---
51
+ // 1. Get arguments excluding the node executable and script name
52
+ const args = process.argv.slice(2);
53
+ // Default settings
54
+ const DEFAULT_PORT = 4000;
55
+ let rootPath = path.resolve(appConfig.workspace.root_dir ?? process.cwd()); // Default to the current working directory
56
+ let port = appConfig.server.port ?? DEFAULT_PORT; //Default port
57
+ const host = appConfig.server.host ?? 'localhost'; //Default host
58
+ let serve = false;
59
+ // Simple argument parsing loop
60
+ for (let i = 0; i < args.length; i++) {
61
+ const arg = args[i];
62
+ if (arg === '--serve') {
63
+ serve = true;
64
+ }
65
+ else if (arg === '--root') {
66
+ // Look at the next argument for the path
67
+ if (i + 1 < args.length) {
68
+ rootPath = path.resolve(args[i + 1]);
69
+ i++; // Skip the next argument since we've consumed it
70
+ }
71
+ else {
72
+ console.error('Error: --root requires a path argument.');
73
+ process.exit(1);
74
+ }
75
+ }
76
+ else if (arg === '--port') {
77
+ // Look at the next argument for the port number
78
+ if (i + 1 < args.length) {
79
+ const portValue = parseInt(args[i + 1], 10);
80
+ if (isNaN(portValue) || portValue <= 0 || portValue > 65535) {
81
+ console.error('Error: --port requires a valid port number (1-65535).');
82
+ process.exit(1);
83
+ }
84
+ port = portValue;
85
+ i++; // Skip the next argument
86
+ }
87
+ else {
88
+ console.error('Error: --port requires a number argument.');
89
+ process.exit(1);
90
+ }
91
+ }
92
+ else if (arg === '-h' || arg === '--help') {
93
+ console.log(`
94
+ Monodog CLI - Monorepo Analysis Engine
95
+
96
+ Usage:
97
+ monodog-cli [options]
98
+
99
+ Options:
100
+ --serve Start the Monorepo Dashboard API server (default: off).
101
+ --root <path> Specify the root directory of the monorepo to analyze (default: current working directory).
102
+ --port <number> Specify the port for the API server (default: 4000).
103
+ -h, --help Show this help message.
104
+
105
+ Example:
106
+ monodog-cli --serve --root /path/to/my/monorepo
107
+ `);
108
+ process.exit(0);
109
+ }
110
+ }
111
+ // --- Execution Logic ---
112
+ if (serve) {
113
+ console.log(`\nInitializing Configurations...`);
114
+ createConfigFileIfMissing(rootPath ?? process.cwd());
115
+ console.log(`Starting Monodog API server...`);
116
+ console.log(`Analyzing monorepo at root: ${rootPath}`);
117
+ // Start the Express server and begin analysis
118
+ (0, index_1.startServer)(rootPath, port, host);
119
+ copyPackageToWorkspace(rootPath);
120
+ }
121
+ else {
122
+ // Default mode: print usage or run a default report if no command is specified
123
+ console.log(`Monodog CLI: No operation specified. Use --serve to start the API or -h for help. Ex: pnpm monodog-cli @monodog/dashboard --serve --root .`);
124
+ }
125
+ /**
126
+ * Copies an installed NPM package from node_modules into the local packages/ workspace directory.
127
+ */
128
+ function copyPackageToWorkspace(rootDir) {
129
+ // 1. Get package name from arguments
130
+ // The package name is expected as the first command-line argument (process.argv[2])
131
+ const packageName = process.argv[2];
132
+ if (!packageName || packageName.startsWith('--')) {
133
+ console.error("Error: Please provide the package name as an argument if you want to setup dashboard.");
134
+ console.log("Usage: pnpm monodog-cli @monodog/dashboard --serve --root .");
135
+ }
136
+ if (packageName !== '@monodog/backend') {
137
+ console.log("\n--- Skipping workspace setup for @monodog/backend to avoid self-copying. ---");
138
+ return;
139
+ }
140
+ // const rootDir = process.cwd();
141
+ const sourcePath = path.join(rootDir, 'node_modules', packageName);
142
+ // Convert package name to a valid folder name (e.g., @scope/name -> scope-name)
143
+ // This is optional but makes file paths cleaner.
144
+ const folderName = packageName.replace('@', '').replace('/', '-');
145
+ const destinationPath = path.join(rootDir, 'packages', folderName);
146
+ console.log(`\n--- Monorepo Workspace Conversion ---`);
147
+ console.log(`Target Package: ${packageName}`);
148
+ console.log(`New Workspace: packages/${folderName}`);
149
+ console.log(`-----------------------------------`);
150
+ // 2. Validate Source existence
151
+ if (!fs.existsSync(sourcePath)) {
152
+ console.error(`\n❌ Error: Source package not found at ${sourcePath}.`);
153
+ console.error("Please ensure the package is installed via 'pnpm install <package-name>' first.");
154
+ process.exit(1);
155
+ }
156
+ // 3. Validate Destination existence (prevent accidental overwrite)
157
+ if (fs.existsSync(destinationPath)) {
158
+ console.error(`\n❌ Error: Destination directory already exists at ${destinationPath}.`);
159
+ console.error("Please manually remove it or rename it before running the script.");
160
+ process.exit(1);
161
+ }
162
+ // Ensure the 'packages' directory exists
163
+ const packagesDir = path.join(rootDir, 'packages');
164
+ if (!fs.existsSync(packagesDir)) {
165
+ fs.mkdirSync(packagesDir, { recursive: true });
166
+ console.log(`Created packages directory: ${packagesDir}`);
167
+ }
168
+ // 4. Perform the copy operation
169
+ try {
170
+ console.log(`\nCopying files from ${sourcePath} to ${destinationPath}...`);
171
+ // fs.cpSync provides cross-platform recursive copying (Node 16.7+)
172
+ fs.cpSync(sourcePath, destinationPath, {
173
+ recursive: true,
174
+ dereference: true,
175
+ // Filter out node_modules inside the package itself to avoid deep recursion
176
+ // filter: (src: string): boolean => !src.includes('node_modules'),
177
+ });
178
+ console.log(`\n✅ Success! Contents of '${packageName}' copied to '${destinationPath}'`);
179
+ // Post-copy instructions
180
+ console.log("\n*** IMPORTANT NEXT STEPS (MANDATORY) ***");
181
+ console.log("1.Migrate Database:");
182
+ console.log(` - pnpm prisma migrate --schema ./node_modules/@monodog/backend/prisma/schema.prisma`);
183
+ console.log("2. Generate Client:");
184
+ console.log(` - pnpm exec prisma generate --schema ./node_modules/@monodog/backend/prisma/schema.prisma`);
185
+ console.log("3. Run Backend app server with dashboard setup");
186
+ console.log(` - pnpm monodog-cli @monodog/dashboard --serve --root .`);
187
+ }
188
+ catch (err) {
189
+ const message = err instanceof Error ? err.message : String(err);
190
+ console.error(`\n❌ Failed to copy files: ${message}`);
191
+ process.exit(1);
192
+ }
193
+ }
194
+ function createConfigFileIfMissing(rootPath) {
195
+ // --- CONFIGURATION ---
196
+ const configFileName = 'monodog-conf.json';
197
+ const configFilePath = path.resolve(rootPath, configFileName);
198
+ // The default content for the configuration file
199
+ const defaultContent = {
200
+ "workspace": {
201
+ "root_dir": "./packages/backend"
202
+ },
203
+ "database": {
204
+ "path": "./monodog.db"
205
+ },
206
+ "server": {
207
+ "host": "0.0.0.0",
208
+ "port": 4000
209
+ }
210
+ };
211
+ const contentString = JSON.stringify(defaultContent, null, 2);
212
+ // ---------------------
213
+ console.log(`\n[monodog] Checking for ${configFileName}...`);
214
+ if (fs.existsSync(configFilePath)) {
215
+ console.log(`[monodog] ${configFileName} already exists. Skipping creation.`);
216
+ }
217
+ else {
218
+ try {
219
+ // Write the default content to the file
220
+ fs.writeFileSync(configFilePath, contentString, 'utf-8');
221
+ console.log(`[monodog] Successfully generated default ${configFileName} in the workspace root.`);
222
+ console.log('[monodog] Please review and update settings like "host" and "port".');
223
+ }
224
+ catch (err) {
225
+ const message = err instanceof Error ? err.message : String(err);
226
+ console.error(`[monodog Error] Failed to generate ${configFileName}:`, message);
227
+ process.exit(1);
228
+ }
229
+ }
230
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadConfig = loadConfig;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ // Global variable to hold the loaded config
40
+ let config = null;
41
+ /**
42
+ * Loads the monodog-conf.json file from the monorepo root.
43
+ * This should be called only once during application startup.
44
+ * @returns The application configuration object.
45
+ */
46
+ function loadConfig() {
47
+ if (config) {
48
+ return config; // Return cached config if already loaded
49
+ }
50
+ // 1. Determine the path to the config file
51
+ // We assume the backend package is running from the monorepo root (cwd is root)
52
+ // or that we can navigate up to the root from the current file's location.
53
+ const rootPath = path.resolve('../../'); // Adjust based on your workspace folder depth from root if needed
54
+ const configPath = path.resolve(rootPath, 'monodog-conf.json');
55
+ if (!fs.existsSync(configPath)) {
56
+ console.error(`ERROR: Configuration file not found at ${configPath}`);
57
+ // A missing config is a critical failure, so we exit
58
+ process.exit(1);
59
+ }
60
+ try {
61
+ // 2. Read and parse the JSON file
62
+ const fileContent = fs.readFileSync(configPath, 'utf-8');
63
+ const parsedConfig = JSON.parse(fileContent);
64
+ // 3. Optional: Add validation logic here (e.g., check if ports are numbers)
65
+ // Cache and return
66
+ config = parsedConfig;
67
+ process.stderr.write(`[Config] Loaded configuration from: ${configPath}`);
68
+ return config;
69
+ }
70
+ catch (error) {
71
+ console.error("ERROR: Failed to read or parse monodog-conf.json.");
72
+ console.error(error);
73
+ process.exit(1);
74
+ }
75
+ }
76
+ // --- Example Usage ---
77
+ // In your main application file (e.g., packages/backend/src/index.ts):
78
+ /*
79
+
80
+ import { loadConfig } from './config-loader';
81
+
82
+ // Load configuration on startup
83
+ const appConfig = loadConfig();
84
+
85
+ // Access the variables easily
86
+ const dbHost = appConfig.database.host;
87
+ const serverPort = appConfig.server.port;
88
+ const workspaceRoot = appConfig.workspace.root_dir;
89
+
90
+ console.log(`\nStarting server on port: ${serverPort}`);
91
+ console.log(`Database connecting to host: ${dbHost}`);
92
+
93
+ // Example server start logic
94
+ // app.listen(serverPort, appConfig.server.host, () => {
95
+ // console.log(`Server running at http://${appConfig.server.host}:${serverPort}`);
96
+ // });
97
+ */
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ /**
3
+ * GitService.ts
4
+ *
5
+ * This service executes native 'git' commands using Node.js's child_process
6
+ * to retrieve the commit history of the local repository.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.GitService = void 0;
13
+ const child_process_1 = require("child_process");
14
+ const util_1 = require("util");
15
+ const path_1 = __importDefault(require("path"));
16
+ // Promisify the standard 'exec' function for easy async/await usage
17
+ const execPromise = (0, util_1.promisify)(child_process_1.exec);
18
+ /**
19
+ * List of standard Conventional Commit types for validation.
20
+ * Any extracted type not in this list will be set to 'other'.
21
+ */
22
+ const VALID_COMMIT_TYPES = [
23
+ 'feat', // New feature
24
+ 'fix', // Bug fix
25
+ 'docs', // Documentation changes
26
+ 'style', // Code style changes (formatting, etc)
27
+ 'refactor', // Code refactoring
28
+ 'perf', // Performance improvements
29
+ 'test', // Adding or updating tests
30
+ 'chore', // Maintenance tasks (e.g., build scripts, dependency updates)
31
+ 'ci', // CI/CD changes
32
+ 'build', // Build system changes (e.g., pnpm/npm scripts)
33
+ 'revert', // Reverting changes
34
+ ];
35
+ /**
36
+ * The delimiter used to separate individual commit records in the git log output.
37
+ * We use a unique string to ensure reliable splitting.
38
+ */
39
+ // const COMMIT_DELIMITER = '|---COMMIT-END---|';
40
+ /**
41
+ * The custom format string passed to 'git log'.
42
+ * It uses JSON structure and our unique delimiter to make parsing consistent.
43
+ * * %H: Commit hash
44
+ * %pn: packageName name
45
+ * %ae: Author email
46
+ * %ad: Author date (ISO 8601 format)
47
+ * %s: Subject (commit message's first line - used for 'message' and 'type' fields)
48
+ */
49
+ // const GIT_LOG_FORMAT = `{"hash": "%H", "author": "%ae", "packageName": "%pn", "date": "%ad", "message": "%s", "type": "%s"}${COMMIT_DELIMITER}`;
50
+ // export class GitService {
51
+ // private repoPath: string;
52
+ // constructor(repoPath: string = process.cwd()) {
53
+ // // Allows specifying a custom path to the monorepo root
54
+ // this.repoPath = repoPath;
55
+ // }
56
+ // /**
57
+ // * Retrieves commit history for the repository, optionally filtered by a package path.
58
+ // * @param pathFilter The path to a package directory (e.g., 'packages/server').
59
+ // * If provided, only commits affecting that path are returned.
60
+ // * @returns A Promise resolving to an array of GitCommit objects.
61
+ // */
62
+ // public async getAllCommits(pathFilter?: string): Promise<GitCommit[]> {
63
+ // const pathArgument = pathFilter ? ` -- ${pathFilter}` : '';
64
+ // const command = `git log --pretty=format:'${GIT_LOG_FORMAT}' --date=iso-strict${pathArgument}`;
65
+ // try {
66
+ // console.log(`Executing Git command: ${command}`);
67
+ // const { stdout } = await execPromise(command, {
68
+ // cwd: this.repoPath,
69
+ // maxBuffer: 1024 * 5000,
70
+ // }); // Increase buffer for large repos
71
+ // // console.log(stdout)
72
+ // console.log(stdout, '--', this.repoPath);
73
+ // const escapedDelimiter = COMMIT_DELIMITER.replace(
74
+ // /[.*+?^${}()|[\]\\]/g,
75
+ // '\\$&'
76
+ // );
77
+ // // 1. Remove the final trailing delimiter, as it creates an empty string at the end of the array
78
+ // const cleanedOutput = stdout
79
+ // .trim()
80
+ // .replace(new RegExp(`${escapedDelimiter}$`), '');
81
+ // console.log(cleanedOutput);
82
+ // if (!cleanedOutput) {
83
+ // return [];
84
+ // }
85
+ // // 2. Split the output by our custom delimiter to get an array of JSON strings
86
+ // const jsonStrings = cleanedOutput.split(COMMIT_DELIMITER);
87
+ // // 3. Parse each string into a GitCommit object
88
+ // const commits: GitCommit[] = jsonStrings
89
+ // .map(jsonString => {
90
+ // try {
91
+ // // JSON.parse is necessary because the output starts as a string
92
+ // const commit = JSON.parse(jsonString);
93
+ // console.log(commit.type, commit['hash']);
94
+ // try {
95
+ // // FIX: Updated Regex to handle optional scope (e.g., 'feat(scope):')
96
+ // // Matches:
97
+ // // 1. (^(\w+)) - The commit type (e.g., 'feat')
98
+ // // 2. (\([^)]+\))? - The optional scope (e.g., '(setup)')
99
+ // // 3. (:|!) - Ends with a colon or an exclamation point (for breaking changes)
100
+ // const typeMatch = commit.type.match(/^(\w+)(\([^)]+\))?([:!])/);
101
+ // let extractedType = 'other';
102
+ // if (typeMatch) {
103
+ // const rawType = typeMatch[1];
104
+ // // NEW: Validate the extracted type against the list of known types
105
+ // if (VALID_COMMIT_TYPES.includes(rawType)) {
106
+ // extractedType = rawType;
107
+ // }
108
+ // }
109
+ // commit.type = extractedType;
110
+ // } catch (e) {
111
+ // console.error('Failed to match commit type:', commit, e);
112
+ // // Skip malformed entries
113
+ // return null;
114
+ // }
115
+ // // // Set type to 'other' if the conventional format is not found
116
+ // // commit.type = typeMatch ? typeMatch[1] : 'other';
117
+ // return commit;
118
+ // } catch (e) {
119
+ // // console.log(jsonString)
120
+ // console.error('Failed to parse commit JSON:', jsonString, e);
121
+ // // Skip malformed entries
122
+ // return null;
123
+ // }
124
+ // })
125
+ // .filter((commit): commit is GitCommit => commit !== null);
126
+ // return commits;
127
+ // } catch (error) {
128
+ // console.error('Error fetching Git history:', error);
129
+ // // In a real application, you would handle this gracefully (e.g., throwing a custom error)
130
+ // throw new Error(
131
+ // 'Failed to retrieve Git commit history. Is Git installed and is the path correct?'
132
+ // );
133
+ // }
134
+ // }
135
+ // }
136
+ class GitService {
137
+ constructor(repoPath = process.cwd()) {
138
+ this.repoPath = repoPath;
139
+ }
140
+ /**
141
+ * Retrieves commit history for the repository, optionally filtered by a package path.
142
+ */
143
+ async getAllCommits(pathFilter) {
144
+ try {
145
+ // First, validate we're in a git repo
146
+ await this.validateGitRepository();
147
+ let pathArgument = '';
148
+ if (pathFilter) {
149
+ // Normalize the path and ensure it's relative to the repo root
150
+ const normalizedPath = this.normalizePath(pathFilter);
151
+ pathArgument = ` -- ${normalizedPath}`;
152
+ }
153
+ // Use a simpler git log format
154
+ const command = `git log --pretty=format:"%H|%an|%ad|%s" --date=iso-strict${pathArgument}`;
155
+ console.log(`🔧 Executing Git command in: ${this.repoPath}`);
156
+ console.log(`📝 Git command: ${command}`);
157
+ const { stdout, stderr } = await execPromise(command, {
158
+ cwd: this.repoPath,
159
+ maxBuffer: 1024 * 5000,
160
+ });
161
+ if (stderr) {
162
+ console.warn('Git stderr:', stderr);
163
+ }
164
+ if (!stdout.trim()) {
165
+ console.log('📭 No commits found for path:', pathFilter);
166
+ return [];
167
+ }
168
+ // Parse the output
169
+ const commits = [];
170
+ const lines = stdout.trim().split('\n');
171
+ for (const line of lines) {
172
+ try {
173
+ const [hash, author, date, message] = line.split('|');
174
+ const commit = {
175
+ hash: hash.trim(),
176
+ author: author.trim(),
177
+ packageName: pathFilter || 'root',
178
+ date: new Date(date.trim()),
179
+ message: message.trim(),
180
+ type: this.extractCommitType(message.trim()),
181
+ };
182
+ commits.push(commit);
183
+ }
184
+ catch (parseError) {
185
+ console.error('❌ Failed to parse commit line:', line, parseError);
186
+ }
187
+ }
188
+ console.log(`✅ Successfully parsed ${commits.length} commits`);
189
+ return commits;
190
+ }
191
+ catch (error) {
192
+ console.error('💥 Error in getAllCommits:', error);
193
+ throw error;
194
+ }
195
+ }
196
+ /**
197
+ * Normalize path to be relative to git repo root
198
+ */
199
+ normalizePath(inputPath) {
200
+ // If it's an absolute path, make it relative to repo root
201
+ if (path_1.default.isAbsolute(inputPath)) {
202
+ return path_1.default.relative(this.repoPath, inputPath);
203
+ }
204
+ // If it's already relative, return as-is
205
+ return inputPath;
206
+ }
207
+ /**
208
+ * Extract commit type from message
209
+ */
210
+ extractCommitType(message) {
211
+ try {
212
+ const typeMatch = message.match(/^(\w+)(\([^)]+\))?!?:/);
213
+ if (typeMatch) {
214
+ const rawType = typeMatch[1].toLowerCase();
215
+ if (VALID_COMMIT_TYPES.includes(rawType)) {
216
+ return rawType;
217
+ }
218
+ }
219
+ return 'other';
220
+ }
221
+ catch (error) {
222
+ return 'other';
223
+ }
224
+ }
225
+ /**
226
+ * Validate that we're in a git repository
227
+ */
228
+ async validateGitRepository() {
229
+ try {
230
+ await execPromise('git rev-parse --is-inside-work-tree', {
231
+ cwd: this.repoPath,
232
+ });
233
+ console.log('✅ Valid git repository');
234
+ }
235
+ catch (error) {
236
+ throw new Error('Not a git repository (or any of the parent directories)');
237
+ }
238
+ }
239
+ }
240
+ exports.GitService = GitService;