mirror of
https://github.com/nilbuild/diffity.git
synced 2026-09-19 07:26:16 +08:00
refactor: extract git to separate package
This commit is contained in:
Generated
+14
@@ -10,10 +10,15 @@
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/cli",
|
||||
"packages/git",
|
||||
"packages/parser",
|
||||
"packages/ui"
|
||||
]
|
||||
},
|
||||
"node_modules/@diffity/git": {
|
||||
"resolved": "packages/git",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@diffity/parser": {
|
||||
"resolved": "packages/parser",
|
||||
"link": true
|
||||
@@ -3661,6 +3666,7 @@
|
||||
"name": "diffity",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@diffity/git": "0.1.0",
|
||||
"@diffity/parser": "0.1.0",
|
||||
"commander": "latest",
|
||||
"open": "latest",
|
||||
@@ -3675,6 +3681,14 @@
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"packages/git": {
|
||||
"name": "@diffity/git",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"packages/parser": {
|
||||
"name": "@diffity/parser",
|
||||
"version": "0.1.0",
|
||||
|
||||
+2
-1
@@ -5,11 +5,12 @@
|
||||
"description": "GitHub-style git diff viewer in the browser",
|
||||
"workspaces": [
|
||||
"packages/cli",
|
||||
"packages/git",
|
||||
"packages/parser",
|
||||
"packages/ui"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npm run build -w @diffity/parser && npm run build -w @diffity/ui && npm run build -w diffity",
|
||||
"build": "npm run build -w @diffity/parser && npm run build -w @diffity/git && npm run build -w @diffity/ui && npm run build -w diffity",
|
||||
"test": "npm run test -w @diffity/parser && npm run test -w @diffity/ui",
|
||||
"dev": "npm run dev -w @diffity/ui",
|
||||
"start": "npm run dev -w diffity"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dev": "tsx src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@diffity/git": "0.1.0",
|
||||
"@diffity/parser": "0.1.0",
|
||||
"commander": "latest",
|
||||
"open": "latest",
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync } from 'node:fs';
|
||||
|
||||
export function isGitRepo(): boolean {
|
||||
try {
|
||||
execSync('git rev-parse --is-inside-work-tree', {
|
||||
stdio: 'pipe',
|
||||
});
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getRepoRoot(): string {
|
||||
return execSync('git rev-parse --show-toplevel', {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
}
|
||||
|
||||
export function getRepoName(): string {
|
||||
const root = getRepoRoot();
|
||||
return root.split('/').pop() || root;
|
||||
}
|
||||
|
||||
export function getCurrentBranch(): string {
|
||||
try {
|
||||
return execSync('git rev-parse --abbrev-ref HEAD', {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
} catch {
|
||||
return 'HEAD';
|
||||
}
|
||||
}
|
||||
|
||||
export function getGitDiff(args: string[] = []): string {
|
||||
const cmd = ['git', 'diff', ...args].join(' ');
|
||||
return execSync(cmd, {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
});
|
||||
}
|
||||
|
||||
export function getUntrackedFiles(): string[] {
|
||||
const output = execSync('git ls-files --others --exclude-standard', {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
|
||||
if (!output) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return output.split('\n');
|
||||
}
|
||||
|
||||
export function getUntrackedDiff(files: string[]): string {
|
||||
const diffs: string[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
execSync(`git diff --no-index -- /dev/null "${file}"`, {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
const error = err as { stdout?: string; status?: number };
|
||||
if (error.status === 1 && error.stdout) {
|
||||
diffs.push(error.stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diffs.join('\n');
|
||||
}
|
||||
|
||||
export function getFileContent(path: string, ref?: string): string {
|
||||
if (ref) {
|
||||
return execSync(`git show ${ref}:${path}`, {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
}
|
||||
|
||||
return execSync(`git show HEAD:${path}`, {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
}
|
||||
|
||||
export function getStagedFiles(): string[] {
|
||||
const output = execSync('git diff --staged --name-only', {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
|
||||
if (!output) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return output.split('\n');
|
||||
}
|
||||
|
||||
export function getUnstagedFiles(): string[] {
|
||||
const output = execSync('git diff --name-only', {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
|
||||
if (!output) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return output.split('\n');
|
||||
}
|
||||
|
||||
export interface Commit {
|
||||
hash: string;
|
||||
shortHash: string;
|
||||
message: string;
|
||||
relativeDate: string;
|
||||
}
|
||||
|
||||
export function getRecentCommits(count: number, skip = 0): Commit[] {
|
||||
const output = execSync(`git log -n ${count} --skip=${skip} --format="%H|%h|%s|%cr"`, {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
|
||||
if (!output) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return output.split('\n').map((line) => {
|
||||
const [hash, shortHash, message, relativeDate] = line.split('|');
|
||||
return { hash, shortHash, message, relativeDate };
|
||||
});
|
||||
}
|
||||
|
||||
export interface RepoInfo {
|
||||
name: string;
|
||||
branch: string;
|
||||
root: string;
|
||||
}
|
||||
|
||||
export function getRepoInfo(): RepoInfo {
|
||||
return {
|
||||
name: getRepoName(),
|
||||
branch: getCurrentBranch(),
|
||||
root: getRepoRoot(),
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Command } from 'commander';
|
||||
import open from 'open';
|
||||
import pc from 'picocolors';
|
||||
import { isGitRepo } from './git.js';
|
||||
import { isGitRepo } from '@diffity/git';
|
||||
import { startServer } from './server.js';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
@@ -6,7 +6,7 @@ import { dirname } from 'node:path';
|
||||
import { parseDiff } from '@diffity/parser';
|
||||
import type { ParsedDiff } from '@diffity/parser';
|
||||
import {
|
||||
getGitDiff,
|
||||
getDiff,
|
||||
getUntrackedFiles,
|
||||
getUntrackedDiff,
|
||||
getRepoInfo,
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
getStagedFiles,
|
||||
getUnstagedFiles,
|
||||
getRecentCommits,
|
||||
} from './git.js';
|
||||
} from '@diffity/git';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -65,13 +65,13 @@ interface ServerResult {
|
||||
function resolveRef(ref: string, extraArgs: string[] = []): string {
|
||||
switch (ref) {
|
||||
case 'staged': {
|
||||
return getGitDiff(['--staged', ...extraArgs]);
|
||||
return getDiff(['--staged', ...extraArgs]);
|
||||
}
|
||||
case 'unstaged': {
|
||||
return getGitDiff(extraArgs);
|
||||
return getDiff(extraArgs);
|
||||
}
|
||||
case 'working': {
|
||||
return getGitDiff(['HEAD', ...extraArgs]);
|
||||
return getDiff(['HEAD', ...extraArgs]);
|
||||
}
|
||||
case 'untracked': {
|
||||
const files = getUntrackedFiles();
|
||||
@@ -81,7 +81,7 @@ function resolveRef(ref: string, extraArgs: string[] = []): string {
|
||||
return getUntrackedDiff(files);
|
||||
}
|
||||
case 'all': {
|
||||
let raw = getGitDiff(['HEAD', ...extraArgs]);
|
||||
let raw = getDiff(['HEAD', ...extraArgs]);
|
||||
const untrackedFiles = getUntrackedFiles();
|
||||
if (untrackedFiles.length > 0) {
|
||||
raw += '\n' + getUntrackedDiff(untrackedFiles);
|
||||
@@ -89,7 +89,7 @@ function resolveRef(ref: string, extraArgs: string[] = []): string {
|
||||
return raw;
|
||||
}
|
||||
default: {
|
||||
return getGitDiff([ref, ...extraArgs]);
|
||||
return getDiff([ref, ...extraArgs]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ export function startServer(options: ServerOptions): Promise<ServerResult> {
|
||||
|
||||
const includeUntracked = diffArgs.length === 0;
|
||||
function getFullDiff(args: string[]): string {
|
||||
let raw = getGitDiff(args);
|
||||
let raw = getDiff(args);
|
||||
if (includeUntracked) {
|
||||
const untrackedFiles = getUntrackedFiles();
|
||||
if (untrackedFiles.length > 0) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [
|
||||
{ "path": "../git" },
|
||||
{ "path": "../parser" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@diffity/git",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.5.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { exec } from './exec.js';
|
||||
import type { Commit } from './types.js';
|
||||
|
||||
export function getRecentCommits(count: number, skip = 0): Commit[] {
|
||||
const output = exec(`git log -n ${count} --skip=${skip} --format="%H|%h|%s|%cr"`);
|
||||
|
||||
if (!output) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return output.split('\n').map((line) => {
|
||||
const [hash, shortHash, message, relativeDate] = line.split('|');
|
||||
return { hash, shortHash, message, relativeDate };
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { exec, execLarge, execLines } from './exec.js';
|
||||
|
||||
export function getDiff(args: string[] = []): string {
|
||||
const cmd = ['git', 'diff', ...args].join(' ');
|
||||
return execLarge(cmd);
|
||||
}
|
||||
|
||||
export function getUntrackedFiles(): string[] {
|
||||
return execLines('git ls-files --others --exclude-standard');
|
||||
}
|
||||
|
||||
export function getUntrackedDiff(files: string[]): string {
|
||||
const diffs: string[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
execLarge(`git diff --no-index -- /dev/null "${file}"`);
|
||||
} catch (err: unknown) {
|
||||
const error = err as { stdout?: string; status?: number };
|
||||
if (error.status === 1 && error.stdout) {
|
||||
diffs.push(error.stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diffs.join('\n');
|
||||
}
|
||||
|
||||
export function getFileContent(path: string, ref = 'HEAD'): string {
|
||||
return exec(`git show ${ref}:${path}`);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { execSync, type StdioOptions } from 'node:child_process';
|
||||
|
||||
const STDIO: StdioOptions = ['pipe', 'pipe', 'pipe'];
|
||||
|
||||
export function exec(cmd: string): string {
|
||||
return execSync(cmd, {
|
||||
encoding: 'utf-8',
|
||||
stdio: STDIO,
|
||||
}).trim();
|
||||
}
|
||||
|
||||
export function execLarge(cmd: string): string {
|
||||
return execSync(cmd, {
|
||||
encoding: 'utf-8',
|
||||
stdio: STDIO,
|
||||
maxBuffer: 50 * 1024 * 1024,
|
||||
});
|
||||
}
|
||||
|
||||
export function execLines(cmd: string): string[] {
|
||||
const output = exec(cmd);
|
||||
if (!output) {
|
||||
return [];
|
||||
}
|
||||
return output.split('\n');
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export type { Commit, RepoInfo } from './types.js';
|
||||
export { isGitRepo, getRepoRoot, getRepoName, getCurrentBranch, getRepoInfo } from './repo.js';
|
||||
export { getDiff, getUntrackedFiles, getUntrackedDiff, getFileContent } from './diff.js';
|
||||
export { getStagedFiles, getUnstagedFiles } from './status.js';
|
||||
export { getRecentCommits } from './commits.js';
|
||||
@@ -0,0 +1,37 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { exec } from './exec.js';
|
||||
import type { RepoInfo } from './types.js';
|
||||
|
||||
export function isGitRepo(): boolean {
|
||||
try {
|
||||
execSync('git rev-parse --is-inside-work-tree', { stdio: 'pipe' });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getRepoRoot(): string {
|
||||
return exec('git rev-parse --show-toplevel');
|
||||
}
|
||||
|
||||
export function getRepoName(): string {
|
||||
const root = getRepoRoot();
|
||||
return root.split('/').pop() || root;
|
||||
}
|
||||
|
||||
export function getCurrentBranch(): string {
|
||||
try {
|
||||
return exec('git rev-parse --abbrev-ref HEAD');
|
||||
} catch {
|
||||
return 'HEAD';
|
||||
}
|
||||
}
|
||||
|
||||
export function getRepoInfo(): RepoInfo {
|
||||
return {
|
||||
name: getRepoName(),
|
||||
branch: getCurrentBranch(),
|
||||
root: getRepoRoot(),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { execLines } from './exec.js';
|
||||
|
||||
export function getStagedFiles(): string[] {
|
||||
return execLines('git diff --staged --name-only');
|
||||
}
|
||||
|
||||
export function getUnstagedFiles(): string[] {
|
||||
return execLines('git diff --name-only');
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface Commit {
|
||||
hash: string;
|
||||
shortHash: string;
|
||||
message: string;
|
||||
relativeDate: string;
|
||||
}
|
||||
|
||||
export interface RepoInfo {
|
||||
name: string;
|
||||
branch: string;
|
||||
root: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
},
|
||||
"references": [
|
||||
{ "path": "./packages/parser" },
|
||||
{ "path": "./packages/git" },
|
||||
{ "path": "./packages/cli" },
|
||||
{ "path": "./packages/ui" }
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user