@misaelabanto/commita 0.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.
@@ -0,0 +1,147 @@
1
+ import { existsSync } from 'fs';
2
+ import { readFile } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import simpleGit, { type SimpleGit } from 'simple-git';
5
+
6
+ export interface FileChange {
7
+ path: string;
8
+ status: string;
9
+ }
10
+
11
+ export class GitService {
12
+ private git: SimpleGit;
13
+ private rootDir: string;
14
+
15
+ constructor(workingDir?: string) {
16
+ this.rootDir = workingDir || process.cwd();
17
+ this.git = simpleGit(this.rootDir);
18
+ }
19
+
20
+ getRootDir(): string {
21
+ return this.rootDir;
22
+ }
23
+
24
+ async init(): Promise<void> {
25
+ try {
26
+ const root = await this.git.revparse(['--show-toplevel']);
27
+ this.rootDir = root.trim();
28
+ this.git = simpleGit(this.rootDir);
29
+ } catch (error) {
30
+ // Fallback to cwd if not a git repo (will likely fail later but handles init)
31
+ }
32
+ }
33
+
34
+ async getStagedChanges(): Promise<FileChange[]> {
35
+ const status = await this.git.status();
36
+ return status.staged.map(path => ({
37
+ path,
38
+ status: 'staged',
39
+ }));
40
+ }
41
+
42
+ async getUnstagedChanges(): Promise<FileChange[]> {
43
+ const status = await this.git.status();
44
+ const modified = status.modified.map(path => ({ path, status: 'modified' }));
45
+ const notAdded = status.not_added.map(path => ({ path, status: 'new' }));
46
+ const deleted = status.deleted.map(path => ({ path, status: 'deleted' }));
47
+
48
+ return [...modified, ...notAdded, ...deleted];
49
+ }
50
+
51
+ async getAllChanges(): Promise<FileChange[]> {
52
+ const staged = await this.getStagedChanges();
53
+ const unstaged = await this.getUnstagedChanges();
54
+ return [...staged, ...unstaged];
55
+ }
56
+
57
+ async getDiff(files: string[], staged: boolean = false): Promise<string> {
58
+ if (files.length === 0) {
59
+ return '';
60
+ }
61
+
62
+ try {
63
+ const status = await this.git.status();
64
+ const newFiles = status.not_added;
65
+ const diffs: string[] = [];
66
+
67
+ for (const file of files) {
68
+ if (newFiles.includes(file) && !staged) {
69
+ const newFileDiff = await this.getNewFileDiff(file);
70
+ if (newFileDiff) {
71
+ diffs.push(newFileDiff);
72
+ }
73
+ } else {
74
+ const diffArgs = staged ? ['--cached', '--', file] : ['--', file];
75
+ const diff = await this.git.diff(diffArgs);
76
+ if (diff) {
77
+ diffs.push(diff);
78
+ }
79
+ }
80
+ }
81
+
82
+ return diffs.join('\n');
83
+ } catch (error) {
84
+ console.error('Error getting diff:', error);
85
+ return '';
86
+ }
87
+ }
88
+
89
+ private async getNewFileDiff(filePath: string): Promise<string> {
90
+ try {
91
+ const fullPath = join(this.rootDir, filePath);
92
+ if (!existsSync(fullPath)) {
93
+ return '';
94
+ }
95
+
96
+ const content = await readFile(fullPath, 'utf-8');
97
+ const lines = content.split('\n');
98
+
99
+ const diffHeader = `diff --git a/${filePath} b/${filePath}
100
+ new file mode 100644
101
+ index 0000000..0000000
102
+ --- /dev/null
103
+ +++ b/${filePath}
104
+ `;
105
+
106
+ const diffLines = lines.map(line => `+${line}`).join('\n');
107
+ return diffHeader + diffLines;
108
+ } catch (error) {
109
+ console.error(`Error reading new file ${filePath}:`, error);
110
+ return '';
111
+ }
112
+ }
113
+
114
+ async stageFiles(files: string[]): Promise<void> {
115
+ if (files.length === 0) return;
116
+ await this.git.add(files);
117
+ }
118
+
119
+ async unstageFiles(files: string[]): Promise<void> {
120
+ if (files.length === 0) return;
121
+ await this.git.reset(['HEAD', '--', ...files]);
122
+ }
123
+
124
+ async commit(message: string): Promise<void> {
125
+ await this.git.commit(message);
126
+ }
127
+
128
+ async getCurrentBranch(): Promise<string> {
129
+ const status = await this.git.status();
130
+ return status.current || 'main';
131
+ }
132
+
133
+ async push(): Promise<void> {
134
+ const branch = await this.getCurrentBranch();
135
+ await this.git.push('origin', branch);
136
+ }
137
+
138
+ async hasRemote(): Promise<boolean> {
139
+ try {
140
+ const remotes = await this.git.getRemotes();
141
+ return remotes.length > 0;
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+ }
147
+
@@ -0,0 +1,89 @@
1
+ import { readdirSync, statSync } from 'fs';
2
+ import { join, relative } from 'path';
3
+
4
+ export interface ProjectBoundary {
5
+ path: string;
6
+ manifestFile: string;
7
+ }
8
+
9
+ const MANIFEST_FILES = [
10
+ 'package.json',
11
+ 'Cargo.toml',
12
+ 'go.mod',
13
+ 'pyproject.toml',
14
+ 'pom.xml',
15
+ 'build.gradle',
16
+ 'composer.json',
17
+ 'pubspec.yaml',
18
+ 'mix.exs',
19
+ 'deno.json',
20
+ ];
21
+
22
+ const SKIP_DIRS = new Set([
23
+ 'node_modules',
24
+ '.git',
25
+ 'dist',
26
+ 'build',
27
+ 'target',
28
+ 'vendor',
29
+ '__pycache__',
30
+ '.venv',
31
+ '.next',
32
+ '.nuxt',
33
+ '.turbo',
34
+ '.cache',
35
+ 'coverage',
36
+ 'out',
37
+ ]);
38
+
39
+ const MAX_DEPTH = 4;
40
+
41
+ export class ProjectDetector {
42
+ static detect(rootDir: string): ProjectBoundary[] {
43
+ const boundaries: ProjectBoundary[] = [];
44
+ this.scan(rootDir, rootDir, 0, boundaries);
45
+ return boundaries;
46
+ }
47
+
48
+ private static scan(
49
+ dir: string,
50
+ rootDir: string,
51
+ depth: number,
52
+ boundaries: ProjectBoundary[],
53
+ ): void {
54
+ if (depth > MAX_DEPTH) return;
55
+
56
+ let entries: string[];
57
+ try {
58
+ entries = readdirSync(dir);
59
+ } catch {
60
+ return;
61
+ }
62
+
63
+ const isRoot = dir === rootDir;
64
+
65
+ if (!isRoot) {
66
+ const manifest = entries.find(e => MANIFEST_FILES.includes(e));
67
+ if (manifest) {
68
+ boundaries.push({
69
+ path: relative(rootDir, dir),
70
+ manifestFile: manifest,
71
+ });
72
+ return; // Stop recursing into this project's internals
73
+ }
74
+ }
75
+
76
+ for (const entry of entries) {
77
+ if (SKIP_DIRS.has(entry) || entry.startsWith('.')) continue;
78
+
79
+ const fullPath = join(dir, entry);
80
+ try {
81
+ if (statSync(fullPath).isDirectory()) {
82
+ this.scan(fullPath, rootDir, depth + 1, boundaries);
83
+ }
84
+ } catch {
85
+ continue;
86
+ }
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,39 @@
1
+ import { minimatch } from 'minimatch';
2
+
3
+ export class PatternMatcher {
4
+ private patterns: string[];
5
+
6
+ constructor(patterns: string[]) {
7
+ this.patterns = patterns;
8
+ }
9
+
10
+ shouldIgnore(filePath: string): boolean {
11
+ if (this.patterns.length === 0) {
12
+ return false;
13
+ }
14
+
15
+ return this.patterns.some(pattern =>
16
+ minimatch(filePath, pattern, { dot: true })
17
+ );
18
+ }
19
+
20
+ filterFiles<T extends { path: string }>(files: T[]): T[] {
21
+ if (this.patterns.length === 0) {
22
+ return files;
23
+ }
24
+
25
+ return files.filter(file => !this.shouldIgnore(file.path));
26
+ }
27
+
28
+ static parsePatterns(patternsString: string): string[] {
29
+ if (!patternsString || patternsString.trim() === '') {
30
+ return [];
31
+ }
32
+
33
+ return patternsString
34
+ .split(',')
35
+ .map(p => p.trim())
36
+ .filter(p => p.length > 0);
37
+ }
38
+ }
39
+
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "target": "ESNext",
5
+ "module": "Preserve",
6
+ "moduleDetection": "force",
7
+ "jsx": "react-jsx",
8
+ "allowJs": true,
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "noEmit": true,
13
+ "strict": true,
14
+ "skipLibCheck": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "noUncheckedIndexedAccess": true,
17
+ "noImplicitOverride": true,
18
+ "noUnusedLocals": false,
19
+ "noUnusedParameters": false,
20
+ "noPropertyAccessFromIndexSignature": false,
21
+ "baseUrl": ".",
22
+ "paths": {
23
+ "@/*": ["./src/*"]
24
+ }
25
+ }
26
+ }