Skip to content

Commit 3900717

Browse files
authored
fix: fix Git operations reliability and improve CLI configuration (#4)
* fix: improve Git command execution and validation Ensure stdout is properly captured by setting stdio to pipe mode and add validation for Git operations to prevent empty hash errors. * fix: improve package info retrieval and CLI configuration Optimize package.json lookup for CommonJS environments and configure Commander.js with proper program name, description and version. * chore: remove execa dependency Replace execa with native Node.js child_process module for better compatibility and reduced dependencies. * chore: add CJS format to build configuration alongside ESM
1 parent 56f7995 commit 3900717

File tree

7 files changed

+109
-73
lines changed

7 files changed

+109
-73
lines changed

package.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"files": ["dist"],
1717
"type": "module",
1818
"bin": {
19-
"git-intent": "./dist/index.js"
19+
"git-intent": "./dist/index.cjs"
2020
},
2121
"publishConfig": {
2222
"access": "public",
@@ -29,9 +29,9 @@
2929
"format": "biome format",
3030
"format:fix": "biome format --write",
3131
"pkg:build": "pnpm pkg:build:macos && pnpm pkg:build:linux && pnpm pkg:build:win",
32-
"pkg:build:macos": "pkg . -t node18-macos-arm64 -o build/git-intent-macos",
33-
"pkg:build:linux": "pkg . -t node18-linux-x64 -o build/git-intent-linux",
34-
"pkg:build:win": "pkg . -t node18-win-x64 -o build/git-intent-win.exe",
32+
"pkg:build:macos": "pkg . --no-bytecode -t node18-macos-arm64 -o build/git-intent-macos",
33+
"pkg:build:linux": "pkg . --no-bytecode -t node18-linux-x64 -o build/git-intent-linux",
34+
"pkg:build:win": "pkg . --no-bytecode -t node18-win-x64 -o build/git-intent-win.exe",
3535
"test": "vitest",
3636
"test:ui": "vitest --ui"
3737
},
@@ -42,7 +42,6 @@
4242
"dependencies": {
4343
"chalk": "^4.1.2",
4444
"commander": "^13.1.0",
45-
"execa": "^5.1.1",
4645
"external-editor": "^3.1.0",
4746
"fs-extra": "^11.3.0",
4847
"nanoid": "^3.3.7",

pnpm-lock.yaml

Lines changed: 0 additions & 51 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
#!/usr/bin/env node
22
import { program } from 'commander';
33
import * as command from './commands/index.js';
4+
import { getPackageInfo } from './utils/get-package-info.js';
45
import { storage } from './utils/storage.js';
56

67
(async () => {
78
await storage.initializeRefs();
89

10+
const { version, description } = getPackageInfo();
11+
912
program
13+
.name('git-intent')
14+
.description(description)
15+
.version(version)
1016
.addCommand(command.add)
1117
.addCommand(command.list)
1218
.addCommand(command.start)

src/utils/get-package-info.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
import path from 'node:path';
2-
import { fileURLToPath } from 'node:url';
32
import fs from 'fs-extra';
43
import type { PackageJson } from 'type-fest';
54

6-
export function getPackageInfo(): PackageJson & { version: string; description: string } {
7-
const __filename = fileURLToPath(import.meta.url);
8-
const __dirname = path.dirname(__filename);
5+
export function getPackageInfo(): { version: string; description: string } {
6+
const possiblePaths = [
7+
path.resolve(__dirname, '../../package.json'),
8+
path.resolve(__dirname, '../package.json'),
9+
path.resolve(process.cwd(), 'package.json'),
10+
];
911

10-
const packageJsonPath = path.join(__dirname, '..', 'package.json');
12+
for (const packageJsonPath of possiblePaths) {
13+
try {
14+
if (fs.existsSync(packageJsonPath)) {
15+
const packageJson = fs.readJSONSync(packageJsonPath) as PackageJson;
1116

12-
return fs.readJSONSync(packageJsonPath);
17+
if (packageJson.name === 'git-intent') {
18+
return {
19+
version: packageJson.version || '0.0.0',
20+
description: packageJson.description || 'Git Intent CLI',
21+
};
22+
}
23+
}
24+
} catch (error) {}
25+
}
26+
27+
return {
28+
version: '0.0.0',
29+
description: 'Git Intent CLI',
30+
};
1331
}

src/utils/git.ts

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,54 @@
1-
import execa from 'execa';
1+
import { spawnSync } from 'node:child_process';
2+
import path from 'node:path';
3+
import fs from 'fs-extra';
24
import { simpleGit } from 'simple-git';
35

6+
function execGit(args: string[], options: { input?: string; cwd?: string } = {}): string {
7+
const { input, cwd } = options;
8+
9+
const result = spawnSync('git', args, {
10+
input: input ? Buffer.from(input) : undefined,
11+
cwd,
12+
encoding: 'utf-8',
13+
stdio: ['pipe', 'pipe', 'pipe'],
14+
});
15+
16+
if (result.status !== 0) {
17+
throw new Error(`Git command failed: git ${args.join(' ')}\n${result.stderr}`);
18+
}
19+
20+
return result.stdout ? result.stdout.trim() : '';
21+
}
22+
423
export const createGit = (cwd?: string) => simpleGit(cwd);
524

6-
export async function checkIsRepo(cwd?: string): Promise<void> {
7-
const git = createGit(cwd);
25+
export async function findGitRoot(startDir: string = process.cwd()): Promise<string> {
26+
const dir = path.resolve(startDir);
27+
const gitDir = path.join(dir, '.git');
28+
if (fs.existsSync(gitDir) && fs.statSync(gitDir).isDirectory()) {
29+
return dir;
30+
}
31+
32+
const parentDir = path.dirname(dir);
33+
if (parentDir === dir) {
34+
throw new Error('Not a git repository (or any of the parent directories)');
35+
}
36+
37+
return findGitRoot(parentDir);
38+
}
39+
40+
export async function checkIsRepo(cwd?: string): Promise<string> {
841
try {
42+
const git = createGit(cwd);
943
await git.checkIsRepo();
44+
45+
return await findGitRoot(cwd);
1046
} catch {
11-
throw new Error('Not a git repository');
47+
try {
48+
return await findGitRoot(cwd);
49+
} catch (error) {
50+
throw new Error('Not a git repository (or any of the parent directories)');
51+
}
1252
}
1353
}
1454

@@ -37,21 +77,40 @@ export async function getStatus(cwd?: string): Promise<string> {
3777
}
3878

3979
export async function hashObject(content: string, cwd?: string): Promise<string> {
40-
const { stdout } = await execa('git', ['hash-object', '-w', '--stdin'], { input: content, cwd });
41-
return stdout.trim();
80+
return execGit(['hash-object', '-w', '--stdin'], { input: content, cwd });
4281
}
4382

4483
export async function createTree(treeContent: string, cwd?: string): Promise<string> {
45-
const { stdout } = await execa('git', ['mktree'], { input: treeContent, cwd });
46-
return stdout.trim();
84+
if (!treeContent || treeContent.trim() === '') {
85+
throw new Error('Invalid tree content: tree content cannot be empty');
86+
}
87+
return execGit(['mktree'], { input: treeContent, cwd });
4788
}
4889

4990
export async function createCommitTree(treeHash: string, message: string, cwd?: string): Promise<string> {
50-
const { stdout } = await execa('git', ['commit-tree', treeHash, '-m', message], { cwd });
51-
return stdout.trim();
91+
if (!treeHash || treeHash.trim() === '') {
92+
throw new Error('Invalid tree hash: tree hash cannot be empty');
93+
}
94+
95+
try {
96+
const result = execGit(['commit-tree', treeHash, '-m', message], { cwd });
97+
98+
if (!result || result.trim() === '') {
99+
throw new Error(`Failed to create commit tree from hash: ${treeHash}`);
100+
}
101+
102+
return result;
103+
} catch (error) {
104+
console.error('Error creating commit tree:', error);
105+
throw error;
106+
}
52107
}
53108

54109
export async function updateRef(refName: string, commitHash: string, cwd?: string): Promise<void> {
110+
if (!commitHash || commitHash.trim() === '') {
111+
throw new Error(`Invalid commit hash: commit hash cannot be empty for ref ${refName}`);
112+
}
113+
55114
const git = createGit(cwd);
56115
await git.raw(['update-ref', refName, commitHash]);
57116
}

src/utils/storage.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export class GitIntentionalCommitStorage {
162162
await checkIsRepo(root);
163163

164164
const refExists = await checkRefExists(`${this.REFS_PREFIX}/commits`, root);
165+
165166
if (!refExists) {
166167
const initialData = this.getInitialData();
167168
const content = JSON.stringify(initialData, null, 2);
@@ -171,6 +172,10 @@ export class GitIntentionalCommitStorage {
171172
const treeHash = await createTree(treeContent, root);
172173
const commitHash = await createCommitTree(treeHash, 'Initialize intent commits', root);
173174

175+
if (!commitHash || commitHash.trim() === '') {
176+
throw new Error('Failed to create commit: commit hash is empty');
177+
}
178+
174179
await updateRef(`${this.REFS_PREFIX}/commits`, commitHash, root);
175180
}
176181
}

tsup.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import { defineConfig } from 'tsup';
22

33
export default defineConfig({
44
entry: ['src/index.ts'],
5-
format: ['esm'],
5+
format: ['esm', 'cjs'],
66
});

0 commit comments

Comments
 (0)