@lakakala/kgit 0.1.1 → 0.2.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/dist/commands/append.d.ts +1 -1
- package/dist/commands/append.js +4 -3
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +36 -0
- package/dist/commands/new.d.ts +1 -1
- package/dist/commands/new.js +4 -3
- package/dist/commands/remove.d.ts +5 -0
- package/dist/commands/remove.js +42 -0
- package/dist/git.d.ts +2 -1
- package/dist/git.js +33 -2
- package/dist/index.js +26 -39
- package/package.json +1 -1
|
@@ -2,5 +2,5 @@ interface ProjectEntry {
|
|
|
2
2
|
name: string;
|
|
3
3
|
branch: string;
|
|
4
4
|
}
|
|
5
|
-
export declare function appendCommand(workspaceName: string, projectEntries: ProjectEntry[]): Promise<void>;
|
|
5
|
+
export declare function appendCommand(workspaceName: string, projectEntries: ProjectEntry[], newBranch?: string): Promise<void>;
|
|
6
6
|
export {};
|
package/dist/commands/append.js
CHANGED
|
@@ -2,9 +2,10 @@ import path from 'node:path';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { loadConfig, findProject } from '../config.js';
|
|
4
4
|
import { addWorktree, isGitRepo } from '../git.js';
|
|
5
|
-
export async function appendCommand(workspaceName, projectEntries) {
|
|
5
|
+
export async function appendCommand(workspaceName, projectEntries, newBranch) {
|
|
6
6
|
const config = loadConfig();
|
|
7
7
|
const targetDir = path.join(config.workspace, workspaceName);
|
|
8
|
+
const branchName = newBranch ?? workspaceName;
|
|
8
9
|
if (!fs.existsSync(targetDir)) {
|
|
9
10
|
throw new Error(`Workspace directory does not exist: ${targetDir}. Use "kgit new" to create it first.`);
|
|
10
11
|
}
|
|
@@ -18,8 +19,8 @@ export async function appendCommand(workspaceName, projectEntries) {
|
|
|
18
19
|
console.warn(`Worktree already exists, skipping: ${worktreePath}`);
|
|
19
20
|
continue;
|
|
20
21
|
}
|
|
21
|
-
console.log(`Adding worktree for "${project.name}" (branch: ${entry.branch}) -> ${worktreePath}`);
|
|
22
|
-
await addWorktree(project.path, worktreePath, entry.branch);
|
|
22
|
+
console.log(`Adding worktree for "${project.name}" (new branch: ${branchName}, base: ${entry.branch}) -> ${worktreePath}`);
|
|
23
|
+
await addWorktree(project.path, worktreePath, branchName, entry.branch);
|
|
23
24
|
}
|
|
24
25
|
console.log(`\nProjects appended to workspace "${workspaceName}" at ${targetDir}`);
|
|
25
26
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function listCommand(): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { loadConfig } from '../config.js';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
async function getCurrentBranch(worktreePath) {
|
|
6
|
+
try {
|
|
7
|
+
const { stdout } = await execa('git', ['-C', worktreePath, 'branch', '--show-current'], { stdio: 'pipe' });
|
|
8
|
+
return stdout.trim() || 'detached HEAD';
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return 'unknown';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function listCommand() {
|
|
15
|
+
const config = loadConfig();
|
|
16
|
+
if (!fs.existsSync(config.workspace)) {
|
|
17
|
+
console.log('No workspaces found (workspace directory does not exist).');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const entries = fs.readdirSync(config.workspace, { withFileTypes: true });
|
|
21
|
+
const workspaces = entries.filter(e => e.isDirectory());
|
|
22
|
+
if (workspaces.length === 0) {
|
|
23
|
+
console.log('No workspaces found.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const ws of workspaces) {
|
|
27
|
+
console.log(`${ws.name}`);
|
|
28
|
+
const wsPath = path.join(config.workspace, ws.name);
|
|
29
|
+
const projects = fs.readdirSync(wsPath, { withFileTypes: true }).filter(e => e.isDirectory());
|
|
30
|
+
for (const proj of projects) {
|
|
31
|
+
const projPath = path.join(wsPath, proj.name);
|
|
32
|
+
const branch = await getCurrentBranch(projPath);
|
|
33
|
+
console.log(` └─ ${proj.name} (${branch})`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/commands/new.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ interface ProjectEntry {
|
|
|
2
2
|
name: string;
|
|
3
3
|
branch: string;
|
|
4
4
|
}
|
|
5
|
-
export declare function newCommand(workspaceName: string, projectEntries: ProjectEntry[]): Promise<void>;
|
|
5
|
+
export declare function newCommand(workspaceName: string, projectEntries: ProjectEntry[], newBranch?: string): Promise<void>;
|
|
6
6
|
export {};
|
package/dist/commands/new.js
CHANGED
|
@@ -2,9 +2,10 @@ import path from 'node:path';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { loadConfig, findProject } from '../config.js';
|
|
4
4
|
import { addWorktree, isGitRepo } from '../git.js';
|
|
5
|
-
export async function newCommand(workspaceName, projectEntries) {
|
|
5
|
+
export async function newCommand(workspaceName, projectEntries, newBranch) {
|
|
6
6
|
const config = loadConfig();
|
|
7
7
|
const targetDir = path.join(config.workspace, workspaceName);
|
|
8
|
+
const branchName = newBranch ?? workspaceName;
|
|
8
9
|
if (fs.existsSync(targetDir)) {
|
|
9
10
|
throw new Error(`Workspace directory already exists: ${targetDir}`);
|
|
10
11
|
}
|
|
@@ -16,8 +17,8 @@ export async function newCommand(workspaceName, projectEntries) {
|
|
|
16
17
|
throw new Error(`Not a git repository: ${project.path}`);
|
|
17
18
|
}
|
|
18
19
|
const worktreePath = path.join(targetDir, project.name);
|
|
19
|
-
console.log(`Adding worktree for "${project.name}" (branch: ${entry.branch}) -> ${worktreePath}`);
|
|
20
|
-
await addWorktree(project.path, worktreePath, entry.branch);
|
|
20
|
+
console.log(`Adding worktree for "${project.name}" (new branch: ${branchName}, base: ${entry.branch}) -> ${worktreePath}`);
|
|
21
|
+
await addWorktree(project.path, worktreePath, branchName, entry.branch);
|
|
21
22
|
}
|
|
22
23
|
console.log(`\nWorkspace "${workspaceName}" created successfully at ${targetDir}`);
|
|
23
24
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { loadConfig, findProject } from '../config.js';
|
|
4
|
+
import { removeWorktree } from '../git.js';
|
|
5
|
+
export async function removeCommand(workspaceName, projectEntries) {
|
|
6
|
+
const config = loadConfig();
|
|
7
|
+
const targetDir = path.join(config.workspace, workspaceName);
|
|
8
|
+
if (!fs.existsSync(targetDir)) {
|
|
9
|
+
throw new Error(`Workspace directory does not exist: ${targetDir}`);
|
|
10
|
+
}
|
|
11
|
+
if (projectEntries.length === 0) {
|
|
12
|
+
// Remove entire workspace: prune all worktrees then delete folder
|
|
13
|
+
const entries = fs.readdirSync(targetDir);
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const worktreePath = path.join(targetDir, entry);
|
|
16
|
+
const project = config.projects.find(p => p.name === entry);
|
|
17
|
+
if (project && fs.statSync(worktreePath).isDirectory()) {
|
|
18
|
+
console.log(`Removing worktree "${entry}" from ${project.path}`);
|
|
19
|
+
await removeWorktree(project.path, worktreePath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
23
|
+
console.log(`Workspace "${workspaceName}" removed.`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
for (const entry of projectEntries) {
|
|
27
|
+
const project = findProject(config, entry.name);
|
|
28
|
+
const worktreePath = path.join(targetDir, project.name);
|
|
29
|
+
if (!fs.existsSync(worktreePath)) {
|
|
30
|
+
console.warn(`Worktree not found, skipping: ${worktreePath}`);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
console.log(`Removing worktree "${project.name}" from ${project.path}`);
|
|
34
|
+
await removeWorktree(project.path, worktreePath);
|
|
35
|
+
}
|
|
36
|
+
// Remove workspace dir if now empty
|
|
37
|
+
if (fs.readdirSync(targetDir).length === 0) {
|
|
38
|
+
fs.rmdirSync(targetDir);
|
|
39
|
+
console.log(`Workspace directory is now empty and has been removed.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/git.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export declare function addWorktree(repoPath: string, worktreePath: string,
|
|
1
|
+
export declare function addWorktree(repoPath: string, worktreePath: string, newBranch: string, baseBranch: string): Promise<void>;
|
|
2
|
+
export declare function removeWorktree(repoPath: string, worktreePath: string): Promise<void>;
|
|
2
3
|
export declare function isGitRepo(dirPath: string): Promise<boolean>;
|
package/dist/git.js
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
|
|
4
|
+
async function localBranchExists(repoPath, branch) {
|
|
5
|
+
const { stdout } = await execa('git', ['-C', repoPath, 'branch', '--list', branch], { stdio: 'pipe' });
|
|
6
|
+
return stdout.trim().length > 0;
|
|
7
|
+
}
|
|
8
|
+
async function remoteBranchExists(repoPath, branch) {
|
|
9
|
+
try {
|
|
10
|
+
const { stdout } = await execa('git', ['-C', repoPath, 'branch', '-r', '--list', `*/${branch}`], { stdio: 'pipe' });
|
|
11
|
+
const match = stdout.trim().split('\n').find(l => l.trim().length > 0);
|
|
12
|
+
return match ? match.trim() : null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function addWorktree(repoPath, worktreePath, newBranch, baseBranch) {
|
|
5
19
|
await fs.promises.mkdir(path.dirname(worktreePath), { recursive: true });
|
|
6
|
-
await
|
|
20
|
+
if (await localBranchExists(repoPath, newBranch)) {
|
|
21
|
+
// Branch exists locally — use it directly
|
|
22
|
+
console.log(` Branch "${newBranch}" exists locally, using it directly.`);
|
|
23
|
+
await execa('git', ['-C', repoPath, 'worktree', 'add', worktreePath, newBranch], { stdio: 'inherit' });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const remoteBranch = await remoteBranchExists(repoPath, newBranch);
|
|
27
|
+
if (remoteBranch) {
|
|
28
|
+
// Branch exists on remote — create a tracking local branch
|
|
29
|
+
console.log(` Branch "${newBranch}" found on remote (${remoteBranch}), creating tracking branch.`);
|
|
30
|
+
await execa('git', ['-C', repoPath, 'worktree', 'add', '--track', '-b', newBranch, worktreePath, remoteBranch], { stdio: 'inherit' });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Branch does not exist — create from base branch
|
|
34
|
+
await execa('git', ['-C', repoPath, 'worktree', 'add', '-b', newBranch, worktreePath, baseBranch], { stdio: 'inherit' });
|
|
35
|
+
}
|
|
36
|
+
export async function removeWorktree(repoPath, worktreePath) {
|
|
37
|
+
await execa('git', ['-C', repoPath, 'worktree', 'remove', worktreePath, '--force'], {
|
|
7
38
|
stdio: 'inherit',
|
|
8
39
|
});
|
|
9
40
|
}
|
package/dist/index.js
CHANGED
|
@@ -2,40 +2,12 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { newCommand } from './commands/new.js';
|
|
4
4
|
import { appendCommand } from './commands/append.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* Pre-process argv to merge `-p project branch` pairs into `-p project:branch`
|
|
8
|
-
* so commander can handle them as single option values.
|
|
9
|
-
*/
|
|
10
|
-
function normalizeProjectFlags(argv) {
|
|
11
|
-
const result = [];
|
|
12
|
-
let i = 0;
|
|
13
|
-
while (i < argv.length) {
|
|
14
|
-
if (argv[i] === '-p') {
|
|
15
|
-
const name = argv[i + 1];
|
|
16
|
-
const branch = argv[i + 2];
|
|
17
|
-
if (!name || name.startsWith('-') || !branch || branch.startsWith('-')) {
|
|
18
|
-
console.error('Error: -p requires two arguments: <project> <branch>');
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
result.push('-p', `${name}:${branch}`);
|
|
22
|
-
i += 3;
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
result.push(argv[i]);
|
|
26
|
-
i++;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
5
|
+
import { removeCommand } from './commands/remove.js';
|
|
6
|
+
import { listCommand } from './commands/list.js';
|
|
31
7
|
function collectProject(value, previous) {
|
|
32
8
|
const colonIdx = value.indexOf(':');
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
const name = value.slice(0, colonIdx);
|
|
38
|
-
const branch = value.slice(colonIdx + 1);
|
|
9
|
+
const name = colonIdx === -1 ? value : value.slice(0, colonIdx);
|
|
10
|
+
const branch = colonIdx === -1 ? 'master' : value.slice(colonIdx + 1);
|
|
39
11
|
return [...previous, { name, branch }];
|
|
40
12
|
}
|
|
41
13
|
const program = new Command();
|
|
@@ -46,26 +18,41 @@ program
|
|
|
46
18
|
program
|
|
47
19
|
.command('new <workspace>')
|
|
48
20
|
.description('Create a new workspace with git worktrees')
|
|
49
|
-
.option('-
|
|
21
|
+
.option('-b <branch>', 'New branch name (defaults to workspace name)')
|
|
22
|
+
.option('-p <project[:branch]>', 'Add a project, optionally with base branch (default: master), repeatable', collectProject, [])
|
|
50
23
|
.action(async (workspace, options) => {
|
|
51
24
|
if (options.p.length === 0) {
|
|
52
|
-
console.error('Error: at least one -p <project
|
|
25
|
+
console.error('Error: at least one -p <project[:branch]> is required');
|
|
53
26
|
process.exit(1);
|
|
54
27
|
}
|
|
55
|
-
await newCommand(workspace, options.p);
|
|
28
|
+
await newCommand(workspace, options.p, options.b);
|
|
56
29
|
});
|
|
57
30
|
program
|
|
58
31
|
.command('append <workspace>')
|
|
59
32
|
.description('Append projects to an existing workspace')
|
|
60
|
-
.option('-
|
|
33
|
+
.option('-b <branch>', 'New branch name (defaults to workspace name)')
|
|
34
|
+
.option('-p <project[:branch]>', 'Add a project, optionally with base branch (default: master), repeatable', collectProject, [])
|
|
61
35
|
.action(async (workspace, options) => {
|
|
62
36
|
if (options.p.length === 0) {
|
|
63
|
-
console.error('Error: at least one -p <project
|
|
37
|
+
console.error('Error: at least one -p <project[:branch]> is required');
|
|
64
38
|
process.exit(1);
|
|
65
39
|
}
|
|
66
|
-
await appendCommand(workspace, options.p);
|
|
40
|
+
await appendCommand(workspace, options.p, options.b);
|
|
41
|
+
});
|
|
42
|
+
program
|
|
43
|
+
.command('remove <workspace>')
|
|
44
|
+
.description('Remove a workspace or specific projects from it')
|
|
45
|
+
.option('-p <project>', 'Remove a specific project (repeatable)', (val, prev) => [...prev, { name: val }], [])
|
|
46
|
+
.action(async (workspace, options) => {
|
|
47
|
+
await removeCommand(workspace, options.p);
|
|
48
|
+
});
|
|
49
|
+
program
|
|
50
|
+
.command('list')
|
|
51
|
+
.description('List all created workspaces and their projects')
|
|
52
|
+
.action(async () => {
|
|
53
|
+
await listCommand();
|
|
67
54
|
});
|
|
68
|
-
program.parseAsync(
|
|
55
|
+
program.parseAsync(process.argv, { from: 'node' }).catch((err) => {
|
|
69
56
|
console.error(`Error: ${err.message}`);
|
|
70
57
|
process.exit(1);
|
|
71
58
|
});
|