@bretwardjames/ghp-core 0.1.7 → 0.2.0-beta.1
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/README.md +19 -105
- package/dist/index.cjs +126 -3
- package/dist/index.d.cts +56 -2
- package/dist/index.d.ts +56 -2
- package/dist/index.js +119 -2
- package/package.json +7 -8
package/README.md
CHANGED
|
@@ -1,123 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bretwardjames/ghp-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared core library for GHP tools - provides GitHub Projects API interactions, types, and utilities.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Part of the [GHP monorepo](https://github.com/bretwardjames/ghp).
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|---------|-------------|
|
|
9
|
-
| **[@bretwardjames/ghp-cli](https://github.com/bretwardjames/ghp-cli)** | Command-line interface for GitHub Projects |
|
|
10
|
-
| **[vscode-gh-projects](https://github.com/bretwardjames/vscode-gh-projects)** | VS Code / Cursor extension with visual boards |
|
|
11
|
-
| **@bretwardjames/ghp-core** | Shared library (this package) |
|
|
7
|
+
## Installation
|
|
12
8
|
|
|
13
|
-
Both the CLI and extension share the same underlying library and are designed to work together.
|
|
14
|
-
|
|
15
|
-
## Quick Install
|
|
16
|
-
|
|
17
|
-
Install both the CLI and VS Code/Cursor extension with a single command:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
curl -fsSL https://raw.githubusercontent.com/bretwardjames/ghp-core/main/install.sh | bash
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
This will:
|
|
24
|
-
1. Install the `ghp` CLI globally via npm
|
|
25
|
-
2. Install the VS Code/Cursor extension from the latest release
|
|
26
|
-
|
|
27
|
-
### Manual Installation
|
|
28
|
-
|
|
29
|
-
**CLI only:**
|
|
30
9
|
```bash
|
|
31
|
-
npm install
|
|
10
|
+
npm install @bretwardjames/ghp-core
|
|
32
11
|
```
|
|
33
12
|
|
|
34
|
-
|
|
35
|
-
Download the `.vsix` from [releases](https://github.com/bretwardjames/vscode-gh-projects/releases) and install:
|
|
36
|
-
```bash
|
|
37
|
-
code --install-extension gh-projects-*.vsix
|
|
38
|
-
# or for Cursor:
|
|
39
|
-
cursor --install-extension gh-projects-*.vsix
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Getting Started
|
|
43
|
-
|
|
44
|
-
1. Authenticate with GitHub:
|
|
45
|
-
```bash
|
|
46
|
-
ghp auth
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
2. View your assigned work:
|
|
50
|
-
```bash
|
|
51
|
-
ghp work
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
3. Open VS Code/Cursor and find the GitHub Projects panel in the sidebar
|
|
55
|
-
|
|
56
|
-
## Features
|
|
57
|
-
|
|
58
|
-
### Shared Across Both Tools
|
|
13
|
+
## Usage
|
|
59
14
|
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
- **Issue Templates** - Create issues using your repo's templates
|
|
15
|
+
This package is primarily used internally by:
|
|
16
|
+
- [@bretwardjames/ghp-cli](https://github.com/bretwardjames/ghp/tree/main/packages/cli) - Command-line tool
|
|
17
|
+
- [gh-projects](https://github.com/bretwardjames/ghp/tree/main/apps/vscode) - VS Code extension
|
|
64
18
|
|
|
65
|
-
|
|
19
|
+
## API
|
|
66
20
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- **Workspace Config** - Share settings with your team via `.ghp/config.json`
|
|
70
|
-
- **Simple List Output** - Integration with fzf, rofi, and other pickers
|
|
21
|
+
```typescript
|
|
22
|
+
import { GitHubAPI, parseIssueUrl, BranchLinker } from '@bretwardjames/ghp-core';
|
|
71
23
|
|
|
72
|
-
|
|
24
|
+
// Create API client
|
|
25
|
+
const api = new GitHubAPI(token);
|
|
73
26
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- **Multi-Select** - Bulk operations on multiple items
|
|
77
|
-
- **Real-Time Sync** - Stay in sync with GitHub
|
|
27
|
+
// Parse issue URLs
|
|
28
|
+
const { owner, repo, number } = parseIssueUrl('https://github.com/owner/repo/issues/123');
|
|
78
29
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
### CLI Configuration (ghp-cli)
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
# View all settings with their sources
|
|
87
|
-
ghp config --show
|
|
88
|
-
|
|
89
|
-
# Edit user config (opens $EDITOR)
|
|
90
|
-
ghp config
|
|
91
|
-
|
|
92
|
-
# Edit workspace config (shared with team)
|
|
93
|
-
ghp config -w
|
|
94
|
-
|
|
95
|
-
# Set individual value
|
|
96
|
-
ghp config mainBranch develop
|
|
30
|
+
// Branch linking
|
|
31
|
+
const linker = new BranchLinker(api);
|
|
32
|
+
await linker.linkBranch(issueNumber, branchName);
|
|
97
33
|
```
|
|
98
34
|
|
|
99
|
-
**Config files:**
|
|
100
|
-
- User: `~/.config/ghp-cli/config.json` (personal overrides)
|
|
101
|
-
- Workspace: `.ghp/config.json` (committed, shared with team)
|
|
102
|
-
|
|
103
|
-
Merge order: defaults -> workspace -> user
|
|
104
|
-
|
|
105
|
-
### Extension Configuration (VS Code)
|
|
106
|
-
|
|
107
|
-
Settings are in VS Code's settings UI under "GitHub Projects", or in your workspace `.vscode/settings.json`.
|
|
108
|
-
|
|
109
|
-
## Links
|
|
110
|
-
|
|
111
|
-
- [ghp-cli Documentation](https://github.com/bretwardjames/ghp-cli)
|
|
112
|
-
- [VS Code Extension Documentation](https://github.com/bretwardjames/vscode-gh-projects)
|
|
113
|
-
- [Report Issues](https://github.com/bretwardjames/ghp-core/issues)
|
|
114
|
-
|
|
115
|
-
## Requirements
|
|
116
|
-
|
|
117
|
-
- Node.js >= 18
|
|
118
|
-
- GitHub account with Projects access
|
|
119
|
-
- VS Code 1.85+ or Cursor (for extension)
|
|
120
|
-
|
|
121
35
|
## License
|
|
122
36
|
|
|
123
37
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -36,10 +36,12 @@ __export(index_exports, {
|
|
|
36
36
|
checkoutBranch: () => checkoutBranch,
|
|
37
37
|
computeSettingsDiff: () => computeSettingsDiff,
|
|
38
38
|
createBranch: () => createBranch,
|
|
39
|
+
createWorktree: () => createWorktree,
|
|
39
40
|
detectRepository: () => detectRepository,
|
|
40
41
|
fetchOrigin: () => fetchOrigin,
|
|
41
42
|
formatConflict: () => formatConflict,
|
|
42
43
|
generateBranchName: () => generateBranchName,
|
|
44
|
+
generateWorktreePath: () => generateWorktreePath,
|
|
43
45
|
getAllBranches: () => getAllBranches,
|
|
44
46
|
getCommitsAhead: () => getCommitsAhead,
|
|
45
47
|
getCommitsBehind: () => getCommitsBehind,
|
|
@@ -49,9 +51,11 @@ __export(index_exports, {
|
|
|
49
51
|
getLocalBranches: () => getLocalBranches,
|
|
50
52
|
getRemoteBranches: () => getRemoteBranches,
|
|
51
53
|
getRepositoryRoot: () => getRepositoryRoot,
|
|
54
|
+
getWorktreeForBranch: () => getWorktreeForBranch,
|
|
52
55
|
hasDifferences: () => hasDifferences,
|
|
53
56
|
hasUncommittedChanges: () => hasUncommittedChanges,
|
|
54
57
|
isGitRepository: () => isGitRepository,
|
|
58
|
+
listWorktrees: () => listWorktrees,
|
|
55
59
|
normalizeVSCodeSettings: () => normalizeVSCodeSettings,
|
|
56
60
|
parseBranchLink: () => parseBranchLink,
|
|
57
61
|
parseGitHubUrl: () => parseGitHubUrl,
|
|
@@ -59,6 +63,7 @@ __export(index_exports, {
|
|
|
59
63
|
pullLatest: () => pullLatest,
|
|
60
64
|
queries: () => queries_exports,
|
|
61
65
|
removeBranchLinkFromBody: () => removeBranchLinkFromBody,
|
|
66
|
+
removeWorktree: () => removeWorktree,
|
|
62
67
|
resolveConflicts: () => resolveConflicts,
|
|
63
68
|
sanitizeForBranchName: () => sanitizeForBranchName,
|
|
64
69
|
setBranchLinkInBody: () => setBranchLinkInBody,
|
|
@@ -66,7 +71,8 @@ __export(index_exports, {
|
|
|
66
71
|
toVSCodeSettings: () => toVSCodeSettings,
|
|
67
72
|
useCli: () => useCli,
|
|
68
73
|
useCustom: () => useCustom,
|
|
69
|
-
useVSCode: () => useVSCode
|
|
74
|
+
useVSCode: () => useVSCode,
|
|
75
|
+
worktreeExists: () => worktreeExists
|
|
70
76
|
});
|
|
71
77
|
module.exports = __toCommonJS(index_exports);
|
|
72
78
|
|
|
@@ -405,7 +411,7 @@ var REMOVE_LABELS_MUTATION = `
|
|
|
405
411
|
var ISSUES_WITH_LABEL_QUERY = `
|
|
406
412
|
query($owner: String!, $name: String!, $labels: [String!]) {
|
|
407
413
|
repository(owner: $owner, name: $name) {
|
|
408
|
-
issues(first: 10, labels: $labels, states: [OPEN]) {
|
|
414
|
+
issues(first: 10, labels: $labels, states: [OPEN, CLOSED]) {
|
|
409
415
|
nodes {
|
|
410
416
|
number
|
|
411
417
|
}
|
|
@@ -556,8 +562,18 @@ var GitHubAPI = class {
|
|
|
556
562
|
owner: repo.owner,
|
|
557
563
|
name: repo.name
|
|
558
564
|
});
|
|
565
|
+
if (!response.repository) {
|
|
566
|
+
throw new Error(`Repository not found: ${repo.owner}/${repo.name}`);
|
|
567
|
+
}
|
|
559
568
|
return response.repository.projectsV2.nodes;
|
|
560
569
|
} catch (error) {
|
|
570
|
+
if (error && typeof error === "object" && "errors" in error) {
|
|
571
|
+
const gqlError = error;
|
|
572
|
+
const notFound = gqlError.errors?.find((e) => e.type === "NOT_FOUND");
|
|
573
|
+
if (notFound) {
|
|
574
|
+
throw new Error(`Repository not found: ${repo.owner}/${repo.name}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
561
577
|
this.handleAuthError(error);
|
|
562
578
|
}
|
|
563
579
|
}
|
|
@@ -1148,6 +1164,7 @@ var BranchLinker = class {
|
|
|
1148
1164
|
// src/git-utils.ts
|
|
1149
1165
|
var import_child_process = require("child_process");
|
|
1150
1166
|
var import_util = require("util");
|
|
1167
|
+
var import_os = require("os");
|
|
1151
1168
|
|
|
1152
1169
|
// src/url-parser.ts
|
|
1153
1170
|
function parseGitHubUrl(url) {
|
|
@@ -1200,6 +1217,28 @@ function buildOrgProjectUrl(org, projectNumber) {
|
|
|
1200
1217
|
}
|
|
1201
1218
|
|
|
1202
1219
|
// src/git-utils.ts
|
|
1220
|
+
function sanitizeForPath(input) {
|
|
1221
|
+
return String(input).replace(/\.\./g, "_").replace(/[;&|`$(){}[\]<>!]/g, "").replace(/\s+/g, "-").replace(/[^a-zA-Z0-9_\-./]/g, "_");
|
|
1222
|
+
}
|
|
1223
|
+
function validateBranchName(branch) {
|
|
1224
|
+
if (!branch || branch.trim().length === 0) {
|
|
1225
|
+
throw new Error("Branch name cannot be empty");
|
|
1226
|
+
}
|
|
1227
|
+
const dangerousChars = /[`$\\!;|&<>(){}[\]'"]/;
|
|
1228
|
+
if (dangerousChars.test(branch)) {
|
|
1229
|
+
throw new Error(`Branch name contains invalid characters: ${branch}`);
|
|
1230
|
+
}
|
|
1231
|
+
const gitInvalidChars = /[\s~^:?*\[\\]/;
|
|
1232
|
+
if (gitInvalidChars.test(branch)) {
|
|
1233
|
+
throw new Error(`Branch name contains invalid git characters: ${branch}`);
|
|
1234
|
+
}
|
|
1235
|
+
if (branch.includes("..")) {
|
|
1236
|
+
throw new Error(`Branch name cannot contain '..': ${branch}`);
|
|
1237
|
+
}
|
|
1238
|
+
if (/^[./]|[./]$/.test(branch)) {
|
|
1239
|
+
throw new Error(`Branch name cannot start or end with '/' or '.': ${branch}`);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1203
1242
|
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
1204
1243
|
async function execGit(command, options = {}) {
|
|
1205
1244
|
const cwd = options.cwd || process.cwd();
|
|
@@ -1347,6 +1386,84 @@ async function getDefaultBranch(options = {}) {
|
|
|
1347
1386
|
}
|
|
1348
1387
|
return "master";
|
|
1349
1388
|
}
|
|
1389
|
+
function validatePath(path) {
|
|
1390
|
+
if (!path || path.trim().length === 0) {
|
|
1391
|
+
throw new Error("Path cannot be empty");
|
|
1392
|
+
}
|
|
1393
|
+
const dangerousChars = /[`$;|&<>(){}[\]'"\n\r]/;
|
|
1394
|
+
if (dangerousChars.test(path)) {
|
|
1395
|
+
throw new Error(`Path contains invalid characters: ${path}`);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
async function createWorktree(worktreePath, branch, options = {}) {
|
|
1399
|
+
validateBranchName(branch);
|
|
1400
|
+
validatePath(worktreePath);
|
|
1401
|
+
const localExists = await branchExists(branch, options);
|
|
1402
|
+
if (localExists) {
|
|
1403
|
+
await execGit(`git worktree add "${worktreePath}" "${branch}"`, options);
|
|
1404
|
+
} else {
|
|
1405
|
+
try {
|
|
1406
|
+
await execGit(`git worktree add "${worktreePath}" -b "${branch}" "origin/${branch}"`, options);
|
|
1407
|
+
} catch {
|
|
1408
|
+
await execGit(`git worktree add -b "${branch}" "${worktreePath}"`, options);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
async function removeWorktree(worktreePath, options = {}, force = false) {
|
|
1413
|
+
validatePath(worktreePath);
|
|
1414
|
+
const forceFlag = force ? "--force" : "";
|
|
1415
|
+
await execGit(`git worktree remove ${forceFlag} "${worktreePath}"`, options);
|
|
1416
|
+
}
|
|
1417
|
+
async function listWorktrees(options = {}) {
|
|
1418
|
+
try {
|
|
1419
|
+
const { stdout } = await execGit("git worktree list --porcelain", options);
|
|
1420
|
+
const worktrees = [];
|
|
1421
|
+
const entries = stdout.trim().split("\n\n");
|
|
1422
|
+
for (const entry of entries) {
|
|
1423
|
+
if (!entry.trim()) continue;
|
|
1424
|
+
const lines = entry.split("\n");
|
|
1425
|
+
const info = {
|
|
1426
|
+
isMain: false,
|
|
1427
|
+
branch: null
|
|
1428
|
+
};
|
|
1429
|
+
for (const line of lines) {
|
|
1430
|
+
if (line.startsWith("worktree ")) {
|
|
1431
|
+
info.path = line.substring(9);
|
|
1432
|
+
} else if (line.startsWith("HEAD ")) {
|
|
1433
|
+
info.head = line.substring(5);
|
|
1434
|
+
} else if (line.startsWith("branch ")) {
|
|
1435
|
+
info.branch = line.substring(7).replace(/^refs\/heads\//, "");
|
|
1436
|
+
} else if (line === "bare") {
|
|
1437
|
+
info.isMain = true;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
if (worktrees.length === 0) {
|
|
1441
|
+
info.isMain = true;
|
|
1442
|
+
}
|
|
1443
|
+
if (info.path && info.head) {
|
|
1444
|
+
worktrees.push(info);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return worktrees;
|
|
1448
|
+
} catch {
|
|
1449
|
+
return [];
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
async function getWorktreeForBranch(branch, options = {}) {
|
|
1453
|
+
const worktrees = await listWorktrees(options);
|
|
1454
|
+
return worktrees.find((wt) => wt.branch === branch) || null;
|
|
1455
|
+
}
|
|
1456
|
+
async function worktreeExists(worktreePath, options = {}) {
|
|
1457
|
+
const worktrees = await listWorktrees(options);
|
|
1458
|
+
return worktrees.some((wt) => wt.path === worktreePath);
|
|
1459
|
+
}
|
|
1460
|
+
function generateWorktreePath(basePath, repoName, identifier) {
|
|
1461
|
+
const safeRepoName = sanitizeForPath(repoName);
|
|
1462
|
+
const safeIdentifier = sanitizeForPath(String(identifier));
|
|
1463
|
+
const expandedBase = basePath.startsWith("~") ? basePath.replace("~", (0, import_os.homedir)()) : basePath;
|
|
1464
|
+
const cleanBase = expandedBase.replace(/\/+$/, "");
|
|
1465
|
+
return `${cleanBase}/${safeRepoName}/${safeIdentifier}`;
|
|
1466
|
+
}
|
|
1350
1467
|
|
|
1351
1468
|
// src/sync.ts
|
|
1352
1469
|
var SYNCABLE_KEYS = [
|
|
@@ -1527,10 +1644,12 @@ function getDiffSummary(diff) {
|
|
|
1527
1644
|
checkoutBranch,
|
|
1528
1645
|
computeSettingsDiff,
|
|
1529
1646
|
createBranch,
|
|
1647
|
+
createWorktree,
|
|
1530
1648
|
detectRepository,
|
|
1531
1649
|
fetchOrigin,
|
|
1532
1650
|
formatConflict,
|
|
1533
1651
|
generateBranchName,
|
|
1652
|
+
generateWorktreePath,
|
|
1534
1653
|
getAllBranches,
|
|
1535
1654
|
getCommitsAhead,
|
|
1536
1655
|
getCommitsBehind,
|
|
@@ -1540,9 +1659,11 @@ function getDiffSummary(diff) {
|
|
|
1540
1659
|
getLocalBranches,
|
|
1541
1660
|
getRemoteBranches,
|
|
1542
1661
|
getRepositoryRoot,
|
|
1662
|
+
getWorktreeForBranch,
|
|
1543
1663
|
hasDifferences,
|
|
1544
1664
|
hasUncommittedChanges,
|
|
1545
1665
|
isGitRepository,
|
|
1666
|
+
listWorktrees,
|
|
1546
1667
|
normalizeVSCodeSettings,
|
|
1547
1668
|
parseBranchLink,
|
|
1548
1669
|
parseGitHubUrl,
|
|
@@ -1550,6 +1671,7 @@ function getDiffSummary(diff) {
|
|
|
1550
1671
|
pullLatest,
|
|
1551
1672
|
queries,
|
|
1552
1673
|
removeBranchLinkFromBody,
|
|
1674
|
+
removeWorktree,
|
|
1553
1675
|
resolveConflicts,
|
|
1554
1676
|
sanitizeForBranchName,
|
|
1555
1677
|
setBranchLinkInBody,
|
|
@@ -1557,5 +1679,6 @@ function getDiffSummary(diff) {
|
|
|
1557
1679
|
toVSCodeSettings,
|
|
1558
1680
|
useCli,
|
|
1559
1681
|
useCustom,
|
|
1560
|
-
useVSCode
|
|
1682
|
+
useVSCode,
|
|
1683
|
+
worktreeExists
|
|
1561
1684
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -645,6 +645,60 @@ declare function getAllBranches(options?: GitOptions): Promise<string[]>;
|
|
|
645
645
|
* Get the default branch name (main or master)
|
|
646
646
|
*/
|
|
647
647
|
declare function getDefaultBranch(options?: GitOptions): Promise<string>;
|
|
648
|
+
/**
|
|
649
|
+
* Information about a git worktree
|
|
650
|
+
*/
|
|
651
|
+
interface WorktreeInfo {
|
|
652
|
+
/** Absolute path to the worktree directory */
|
|
653
|
+
path: string;
|
|
654
|
+
/** Commit SHA the worktree is at */
|
|
655
|
+
head: string;
|
|
656
|
+
/** Branch name (without refs/heads/ prefix), or null if detached */
|
|
657
|
+
branch: string | null;
|
|
658
|
+
/** Whether this is the main worktree (the original repo) */
|
|
659
|
+
isMain: boolean;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Create a new worktree for a branch
|
|
663
|
+
* @param worktreePath - Path where the worktree will be created
|
|
664
|
+
* @param branch - Branch to checkout in the worktree
|
|
665
|
+
* @param options - Git options (cwd determines the source repository)
|
|
666
|
+
*/
|
|
667
|
+
declare function createWorktree(worktreePath: string, branch: string, options?: GitOptions): Promise<void>;
|
|
668
|
+
/**
|
|
669
|
+
* Remove a worktree
|
|
670
|
+
* @param worktreePath - Path to the worktree to remove
|
|
671
|
+
* @param options - Git options
|
|
672
|
+
* @param force - Force removal even if worktree has uncommitted changes
|
|
673
|
+
*/
|
|
674
|
+
declare function removeWorktree(worktreePath: string, options?: GitOptions, force?: boolean): Promise<void>;
|
|
675
|
+
/**
|
|
676
|
+
* List all worktrees for the repository
|
|
677
|
+
* @param options - Git options
|
|
678
|
+
* @returns Array of worktree information
|
|
679
|
+
*/
|
|
680
|
+
declare function listWorktrees(options?: GitOptions): Promise<WorktreeInfo[]>;
|
|
681
|
+
/**
|
|
682
|
+
* Get worktree for a specific branch
|
|
683
|
+
* @param branch - Branch name to find
|
|
684
|
+
* @param options - Git options
|
|
685
|
+
* @returns Worktree info if found, null otherwise
|
|
686
|
+
*/
|
|
687
|
+
declare function getWorktreeForBranch(branch: string, options?: GitOptions): Promise<WorktreeInfo | null>;
|
|
688
|
+
/**
|
|
689
|
+
* Check if a worktree exists at the given path
|
|
690
|
+
* @param worktreePath - Path to check
|
|
691
|
+
* @param options - Git options
|
|
692
|
+
*/
|
|
693
|
+
declare function worktreeExists(worktreePath: string, options?: GitOptions): Promise<boolean>;
|
|
694
|
+
/**
|
|
695
|
+
* Generate a worktree path based on repo and branch info
|
|
696
|
+
* @param basePath - Base directory for worktrees (e.g., ~/.ghp/worktrees)
|
|
697
|
+
* @param repoName - Repository name
|
|
698
|
+
* @param identifier - Issue number or branch name to use as identifier
|
|
699
|
+
* @returns Full path to the worktree directory
|
|
700
|
+
*/
|
|
701
|
+
declare function generateWorktreePath(basePath: string, repoName: string, identifier: string | number): string;
|
|
648
702
|
|
|
649
703
|
/**
|
|
650
704
|
* URL parsing utilities for GitHub repositories and issues.
|
|
@@ -937,7 +991,7 @@ declare const REMOVE_LABELS_MUTATION = "\n mutation($issueId: ID!, $labelIds:
|
|
|
937
991
|
/**
|
|
938
992
|
* Query to find issues with a specific label
|
|
939
993
|
*/
|
|
940
|
-
declare const ISSUES_WITH_LABEL_QUERY = "\n query($owner: String!, $name: String!, $labels: [String!]) {\n repository(owner: $owner, name: $name) {\n issues(first: 10, labels: $labels, states: [OPEN]) {\n nodes {\n number\n }\n }\n }\n }\n";
|
|
994
|
+
declare const ISSUES_WITH_LABEL_QUERY = "\n query($owner: String!, $name: String!, $labels: [String!]) {\n repository(owner: $owner, name: $name) {\n issues(first: 10, labels: $labels, states: [OPEN, CLOSED]) {\n nodes {\n number\n }\n }\n }\n }\n";
|
|
941
995
|
/**
|
|
942
996
|
* Query to get available issue types for a repository
|
|
943
997
|
*/
|
|
@@ -988,4 +1042,4 @@ declare namespace queries {
|
|
|
988
1042
|
export { queries_ADD_COMMENT_MUTATION as ADD_COMMENT_MUTATION, queries_ADD_LABELS_MUTATION as ADD_LABELS_MUTATION, queries_ADD_TO_PROJECT_MUTATION as ADD_TO_PROJECT_MUTATION, queries_COLLABORATORS_QUERY as COLLABORATORS_QUERY, queries_CREATE_ISSUE_MUTATION as CREATE_ISSUE_MUTATION, queries_ISSUES_WITH_LABEL_QUERY as ISSUES_WITH_LABEL_QUERY, queries_ISSUE_AND_LABEL_QUERY as ISSUE_AND_LABEL_QUERY, queries_ISSUE_DETAILS_QUERY as ISSUE_DETAILS_QUERY, queries_ISSUE_FOR_UPDATE_QUERY as ISSUE_FOR_UPDATE_QUERY, queries_ISSUE_NODE_ID_QUERY as ISSUE_NODE_ID_QUERY, queries_ISSUE_TYPES_QUERY as ISSUE_TYPES_QUERY, queries_LABEL_EXISTS_QUERY as LABEL_EXISTS_QUERY, queries_PROJECT_FIELDS_QUERY as PROJECT_FIELDS_QUERY, queries_PROJECT_ITEMS_QUERY as PROJECT_ITEMS_QUERY, queries_PROJECT_VIEWS_QUERY as PROJECT_VIEWS_QUERY, queries_RECENT_ISSUES_QUERY as RECENT_ISSUES_QUERY, queries_REMOVE_LABELS_MUTATION as REMOVE_LABELS_MUTATION, queries_REPOSITORY_ID_QUERY as REPOSITORY_ID_QUERY, queries_REPOSITORY_PROJECTS_QUERY as REPOSITORY_PROJECTS_QUERY, queries_UPDATE_ISSUE_BODY_MUTATION as UPDATE_ISSUE_BODY_MUTATION, queries_UPDATE_ISSUE_MUTATION as UPDATE_ISSUE_MUTATION, queries_UPDATE_ISSUE_TYPE_MUTATION as UPDATE_ISSUE_TYPE_MUTATION, queries_UPDATE_ITEM_FIELD_MUTATION as UPDATE_ITEM_FIELD_MUTATION, queries_UPDATE_ITEM_STATUS_MUTATION as UPDATE_ITEM_STATUS_MUTATION, queries_VIEWER_QUERY as VIEWER_QUERY };
|
|
989
1043
|
}
|
|
990
1044
|
|
|
991
|
-
export { type AssigneeInfo, type AuthError, BranchLinker, CLI_TO_VSCODE_MAP, type Collaborator, type ConflictChoices, type ConflictResolution, DEFAULT_VALUES, type DateFieldValue, type FieldInfo, type FieldValue, type FieldValueConnection, GitHubAPI, type GitHubAPIOptions, type GitOptions, type IssueDetails, type IssueReference, type IterationFieldValue, type LabelInfo, type NumberFieldValue, type Project, type ProjectConfig, type ProjectItem, type ProjectItemContent, type ProjectItemsQueryResponse, type ProjectV2, type ProjectV2Field, type ProjectV2Item, type ProjectV2View, type ProjectWithViews, type ProjectsQueryResponse, type RepoInfo, type ResolvedSettings, SETTING_DISPLAY_NAMES, SYNCABLE_KEYS, type SettingConflict, type SettingsDiff, type SettingsSource, type SingleSelectFieldValue, type StatusField, type SyncableSettingKey, type SyncableSettings, type TextFieldValue, type TokenProvider, VSCODE_TO_CLI_MAP, branchExists, buildIssueUrl, buildOrgProjectUrl, buildProjectUrl, buildPullRequestUrl, buildRepoUrl, checkoutBranch, computeSettingsDiff, createBranch, detectRepository, fetchOrigin, formatConflict, generateBranchName, getAllBranches, getCommitsAhead, getCommitsBehind, getCurrentBranch, getDefaultBranch, getDiffSummary, getLocalBranches, getRemoteBranches, getRepositoryRoot, hasDifferences, hasUncommittedChanges, isGitRepository, normalizeVSCodeSettings, parseBranchLink, parseGitHubUrl, parseIssueUrl, pullLatest, queries, removeBranchLinkFromBody, resolveConflicts, sanitizeForBranchName, setBranchLinkInBody, skip, toVSCodeSettings, useCli, useCustom, useVSCode };
|
|
1045
|
+
export { type AssigneeInfo, type AuthError, BranchLinker, CLI_TO_VSCODE_MAP, type Collaborator, type ConflictChoices, type ConflictResolution, DEFAULT_VALUES, type DateFieldValue, type FieldInfo, type FieldValue, type FieldValueConnection, GitHubAPI, type GitHubAPIOptions, type GitOptions, type IssueDetails, type IssueReference, type IterationFieldValue, type LabelInfo, type NumberFieldValue, type Project, type ProjectConfig, type ProjectItem, type ProjectItemContent, type ProjectItemsQueryResponse, type ProjectV2, type ProjectV2Field, type ProjectV2Item, type ProjectV2View, type ProjectWithViews, type ProjectsQueryResponse, type RepoInfo, type ResolvedSettings, SETTING_DISPLAY_NAMES, SYNCABLE_KEYS, type SettingConflict, type SettingsDiff, type SettingsSource, type SingleSelectFieldValue, type StatusField, type SyncableSettingKey, type SyncableSettings, type TextFieldValue, type TokenProvider, VSCODE_TO_CLI_MAP, type WorktreeInfo, branchExists, buildIssueUrl, buildOrgProjectUrl, buildProjectUrl, buildPullRequestUrl, buildRepoUrl, checkoutBranch, computeSettingsDiff, createBranch, createWorktree, detectRepository, fetchOrigin, formatConflict, generateBranchName, generateWorktreePath, getAllBranches, getCommitsAhead, getCommitsBehind, getCurrentBranch, getDefaultBranch, getDiffSummary, getLocalBranches, getRemoteBranches, getRepositoryRoot, getWorktreeForBranch, hasDifferences, hasUncommittedChanges, isGitRepository, listWorktrees, normalizeVSCodeSettings, parseBranchLink, parseGitHubUrl, parseIssueUrl, pullLatest, queries, removeBranchLinkFromBody, removeWorktree, resolveConflicts, sanitizeForBranchName, setBranchLinkInBody, skip, toVSCodeSettings, useCli, useCustom, useVSCode, worktreeExists };
|
package/dist/index.d.ts
CHANGED
|
@@ -645,6 +645,60 @@ declare function getAllBranches(options?: GitOptions): Promise<string[]>;
|
|
|
645
645
|
* Get the default branch name (main or master)
|
|
646
646
|
*/
|
|
647
647
|
declare function getDefaultBranch(options?: GitOptions): Promise<string>;
|
|
648
|
+
/**
|
|
649
|
+
* Information about a git worktree
|
|
650
|
+
*/
|
|
651
|
+
interface WorktreeInfo {
|
|
652
|
+
/** Absolute path to the worktree directory */
|
|
653
|
+
path: string;
|
|
654
|
+
/** Commit SHA the worktree is at */
|
|
655
|
+
head: string;
|
|
656
|
+
/** Branch name (without refs/heads/ prefix), or null if detached */
|
|
657
|
+
branch: string | null;
|
|
658
|
+
/** Whether this is the main worktree (the original repo) */
|
|
659
|
+
isMain: boolean;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Create a new worktree for a branch
|
|
663
|
+
* @param worktreePath - Path where the worktree will be created
|
|
664
|
+
* @param branch - Branch to checkout in the worktree
|
|
665
|
+
* @param options - Git options (cwd determines the source repository)
|
|
666
|
+
*/
|
|
667
|
+
declare function createWorktree(worktreePath: string, branch: string, options?: GitOptions): Promise<void>;
|
|
668
|
+
/**
|
|
669
|
+
* Remove a worktree
|
|
670
|
+
* @param worktreePath - Path to the worktree to remove
|
|
671
|
+
* @param options - Git options
|
|
672
|
+
* @param force - Force removal even if worktree has uncommitted changes
|
|
673
|
+
*/
|
|
674
|
+
declare function removeWorktree(worktreePath: string, options?: GitOptions, force?: boolean): Promise<void>;
|
|
675
|
+
/**
|
|
676
|
+
* List all worktrees for the repository
|
|
677
|
+
* @param options - Git options
|
|
678
|
+
* @returns Array of worktree information
|
|
679
|
+
*/
|
|
680
|
+
declare function listWorktrees(options?: GitOptions): Promise<WorktreeInfo[]>;
|
|
681
|
+
/**
|
|
682
|
+
* Get worktree for a specific branch
|
|
683
|
+
* @param branch - Branch name to find
|
|
684
|
+
* @param options - Git options
|
|
685
|
+
* @returns Worktree info if found, null otherwise
|
|
686
|
+
*/
|
|
687
|
+
declare function getWorktreeForBranch(branch: string, options?: GitOptions): Promise<WorktreeInfo | null>;
|
|
688
|
+
/**
|
|
689
|
+
* Check if a worktree exists at the given path
|
|
690
|
+
* @param worktreePath - Path to check
|
|
691
|
+
* @param options - Git options
|
|
692
|
+
*/
|
|
693
|
+
declare function worktreeExists(worktreePath: string, options?: GitOptions): Promise<boolean>;
|
|
694
|
+
/**
|
|
695
|
+
* Generate a worktree path based on repo and branch info
|
|
696
|
+
* @param basePath - Base directory for worktrees (e.g., ~/.ghp/worktrees)
|
|
697
|
+
* @param repoName - Repository name
|
|
698
|
+
* @param identifier - Issue number or branch name to use as identifier
|
|
699
|
+
* @returns Full path to the worktree directory
|
|
700
|
+
*/
|
|
701
|
+
declare function generateWorktreePath(basePath: string, repoName: string, identifier: string | number): string;
|
|
648
702
|
|
|
649
703
|
/**
|
|
650
704
|
* URL parsing utilities for GitHub repositories and issues.
|
|
@@ -937,7 +991,7 @@ declare const REMOVE_LABELS_MUTATION = "\n mutation($issueId: ID!, $labelIds:
|
|
|
937
991
|
/**
|
|
938
992
|
* Query to find issues with a specific label
|
|
939
993
|
*/
|
|
940
|
-
declare const ISSUES_WITH_LABEL_QUERY = "\n query($owner: String!, $name: String!, $labels: [String!]) {\n repository(owner: $owner, name: $name) {\n issues(first: 10, labels: $labels, states: [OPEN]) {\n nodes {\n number\n }\n }\n }\n }\n";
|
|
994
|
+
declare const ISSUES_WITH_LABEL_QUERY = "\n query($owner: String!, $name: String!, $labels: [String!]) {\n repository(owner: $owner, name: $name) {\n issues(first: 10, labels: $labels, states: [OPEN, CLOSED]) {\n nodes {\n number\n }\n }\n }\n }\n";
|
|
941
995
|
/**
|
|
942
996
|
* Query to get available issue types for a repository
|
|
943
997
|
*/
|
|
@@ -988,4 +1042,4 @@ declare namespace queries {
|
|
|
988
1042
|
export { queries_ADD_COMMENT_MUTATION as ADD_COMMENT_MUTATION, queries_ADD_LABELS_MUTATION as ADD_LABELS_MUTATION, queries_ADD_TO_PROJECT_MUTATION as ADD_TO_PROJECT_MUTATION, queries_COLLABORATORS_QUERY as COLLABORATORS_QUERY, queries_CREATE_ISSUE_MUTATION as CREATE_ISSUE_MUTATION, queries_ISSUES_WITH_LABEL_QUERY as ISSUES_WITH_LABEL_QUERY, queries_ISSUE_AND_LABEL_QUERY as ISSUE_AND_LABEL_QUERY, queries_ISSUE_DETAILS_QUERY as ISSUE_DETAILS_QUERY, queries_ISSUE_FOR_UPDATE_QUERY as ISSUE_FOR_UPDATE_QUERY, queries_ISSUE_NODE_ID_QUERY as ISSUE_NODE_ID_QUERY, queries_ISSUE_TYPES_QUERY as ISSUE_TYPES_QUERY, queries_LABEL_EXISTS_QUERY as LABEL_EXISTS_QUERY, queries_PROJECT_FIELDS_QUERY as PROJECT_FIELDS_QUERY, queries_PROJECT_ITEMS_QUERY as PROJECT_ITEMS_QUERY, queries_PROJECT_VIEWS_QUERY as PROJECT_VIEWS_QUERY, queries_RECENT_ISSUES_QUERY as RECENT_ISSUES_QUERY, queries_REMOVE_LABELS_MUTATION as REMOVE_LABELS_MUTATION, queries_REPOSITORY_ID_QUERY as REPOSITORY_ID_QUERY, queries_REPOSITORY_PROJECTS_QUERY as REPOSITORY_PROJECTS_QUERY, queries_UPDATE_ISSUE_BODY_MUTATION as UPDATE_ISSUE_BODY_MUTATION, queries_UPDATE_ISSUE_MUTATION as UPDATE_ISSUE_MUTATION, queries_UPDATE_ISSUE_TYPE_MUTATION as UPDATE_ISSUE_TYPE_MUTATION, queries_UPDATE_ITEM_FIELD_MUTATION as UPDATE_ITEM_FIELD_MUTATION, queries_UPDATE_ITEM_STATUS_MUTATION as UPDATE_ITEM_STATUS_MUTATION, queries_VIEWER_QUERY as VIEWER_QUERY };
|
|
989
1043
|
}
|
|
990
1044
|
|
|
991
|
-
export { type AssigneeInfo, type AuthError, BranchLinker, CLI_TO_VSCODE_MAP, type Collaborator, type ConflictChoices, type ConflictResolution, DEFAULT_VALUES, type DateFieldValue, type FieldInfo, type FieldValue, type FieldValueConnection, GitHubAPI, type GitHubAPIOptions, type GitOptions, type IssueDetails, type IssueReference, type IterationFieldValue, type LabelInfo, type NumberFieldValue, type Project, type ProjectConfig, type ProjectItem, type ProjectItemContent, type ProjectItemsQueryResponse, type ProjectV2, type ProjectV2Field, type ProjectV2Item, type ProjectV2View, type ProjectWithViews, type ProjectsQueryResponse, type RepoInfo, type ResolvedSettings, SETTING_DISPLAY_NAMES, SYNCABLE_KEYS, type SettingConflict, type SettingsDiff, type SettingsSource, type SingleSelectFieldValue, type StatusField, type SyncableSettingKey, type SyncableSettings, type TextFieldValue, type TokenProvider, VSCODE_TO_CLI_MAP, branchExists, buildIssueUrl, buildOrgProjectUrl, buildProjectUrl, buildPullRequestUrl, buildRepoUrl, checkoutBranch, computeSettingsDiff, createBranch, detectRepository, fetchOrigin, formatConflict, generateBranchName, getAllBranches, getCommitsAhead, getCommitsBehind, getCurrentBranch, getDefaultBranch, getDiffSummary, getLocalBranches, getRemoteBranches, getRepositoryRoot, hasDifferences, hasUncommittedChanges, isGitRepository, normalizeVSCodeSettings, parseBranchLink, parseGitHubUrl, parseIssueUrl, pullLatest, queries, removeBranchLinkFromBody, resolveConflicts, sanitizeForBranchName, setBranchLinkInBody, skip, toVSCodeSettings, useCli, useCustom, useVSCode };
|
|
1045
|
+
export { type AssigneeInfo, type AuthError, BranchLinker, CLI_TO_VSCODE_MAP, type Collaborator, type ConflictChoices, type ConflictResolution, DEFAULT_VALUES, type DateFieldValue, type FieldInfo, type FieldValue, type FieldValueConnection, GitHubAPI, type GitHubAPIOptions, type GitOptions, type IssueDetails, type IssueReference, type IterationFieldValue, type LabelInfo, type NumberFieldValue, type Project, type ProjectConfig, type ProjectItem, type ProjectItemContent, type ProjectItemsQueryResponse, type ProjectV2, type ProjectV2Field, type ProjectV2Item, type ProjectV2View, type ProjectWithViews, type ProjectsQueryResponse, type RepoInfo, type ResolvedSettings, SETTING_DISPLAY_NAMES, SYNCABLE_KEYS, type SettingConflict, type SettingsDiff, type SettingsSource, type SingleSelectFieldValue, type StatusField, type SyncableSettingKey, type SyncableSettings, type TextFieldValue, type TokenProvider, VSCODE_TO_CLI_MAP, type WorktreeInfo, branchExists, buildIssueUrl, buildOrgProjectUrl, buildProjectUrl, buildPullRequestUrl, buildRepoUrl, checkoutBranch, computeSettingsDiff, createBranch, createWorktree, detectRepository, fetchOrigin, formatConflict, generateBranchName, generateWorktreePath, getAllBranches, getCommitsAhead, getCommitsBehind, getCurrentBranch, getDefaultBranch, getDiffSummary, getLocalBranches, getRemoteBranches, getRepositoryRoot, getWorktreeForBranch, hasDifferences, hasUncommittedChanges, isGitRepository, listWorktrees, normalizeVSCodeSettings, parseBranchLink, parseGitHubUrl, parseIssueUrl, pullLatest, queries, removeBranchLinkFromBody, removeWorktree, resolveConflicts, sanitizeForBranchName, setBranchLinkInBody, skip, toVSCodeSettings, useCli, useCustom, useVSCode, worktreeExists };
|
package/dist/index.js
CHANGED
|
@@ -339,7 +339,7 @@ var REMOVE_LABELS_MUTATION = `
|
|
|
339
339
|
var ISSUES_WITH_LABEL_QUERY = `
|
|
340
340
|
query($owner: String!, $name: String!, $labels: [String!]) {
|
|
341
341
|
repository(owner: $owner, name: $name) {
|
|
342
|
-
issues(first: 10, labels: $labels, states: [OPEN]) {
|
|
342
|
+
issues(first: 10, labels: $labels, states: [OPEN, CLOSED]) {
|
|
343
343
|
nodes {
|
|
344
344
|
number
|
|
345
345
|
}
|
|
@@ -490,8 +490,18 @@ var GitHubAPI = class {
|
|
|
490
490
|
owner: repo.owner,
|
|
491
491
|
name: repo.name
|
|
492
492
|
});
|
|
493
|
+
if (!response.repository) {
|
|
494
|
+
throw new Error(`Repository not found: ${repo.owner}/${repo.name}`);
|
|
495
|
+
}
|
|
493
496
|
return response.repository.projectsV2.nodes;
|
|
494
497
|
} catch (error) {
|
|
498
|
+
if (error && typeof error === "object" && "errors" in error) {
|
|
499
|
+
const gqlError = error;
|
|
500
|
+
const notFound = gqlError.errors?.find((e) => e.type === "NOT_FOUND");
|
|
501
|
+
if (notFound) {
|
|
502
|
+
throw new Error(`Repository not found: ${repo.owner}/${repo.name}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
495
505
|
this.handleAuthError(error);
|
|
496
506
|
}
|
|
497
507
|
}
|
|
@@ -1082,6 +1092,7 @@ var BranchLinker = class {
|
|
|
1082
1092
|
// src/git-utils.ts
|
|
1083
1093
|
import { exec } from "child_process";
|
|
1084
1094
|
import { promisify } from "util";
|
|
1095
|
+
import { homedir } from "os";
|
|
1085
1096
|
|
|
1086
1097
|
// src/url-parser.ts
|
|
1087
1098
|
function parseGitHubUrl(url) {
|
|
@@ -1134,6 +1145,28 @@ function buildOrgProjectUrl(org, projectNumber) {
|
|
|
1134
1145
|
}
|
|
1135
1146
|
|
|
1136
1147
|
// src/git-utils.ts
|
|
1148
|
+
function sanitizeForPath(input) {
|
|
1149
|
+
return String(input).replace(/\.\./g, "_").replace(/[;&|`$(){}[\]<>!]/g, "").replace(/\s+/g, "-").replace(/[^a-zA-Z0-9_\-./]/g, "_");
|
|
1150
|
+
}
|
|
1151
|
+
function validateBranchName(branch) {
|
|
1152
|
+
if (!branch || branch.trim().length === 0) {
|
|
1153
|
+
throw new Error("Branch name cannot be empty");
|
|
1154
|
+
}
|
|
1155
|
+
const dangerousChars = /[`$\\!;|&<>(){}[\]'"]/;
|
|
1156
|
+
if (dangerousChars.test(branch)) {
|
|
1157
|
+
throw new Error(`Branch name contains invalid characters: ${branch}`);
|
|
1158
|
+
}
|
|
1159
|
+
const gitInvalidChars = /[\s~^:?*\[\\]/;
|
|
1160
|
+
if (gitInvalidChars.test(branch)) {
|
|
1161
|
+
throw new Error(`Branch name contains invalid git characters: ${branch}`);
|
|
1162
|
+
}
|
|
1163
|
+
if (branch.includes("..")) {
|
|
1164
|
+
throw new Error(`Branch name cannot contain '..': ${branch}`);
|
|
1165
|
+
}
|
|
1166
|
+
if (/^[./]|[./]$/.test(branch)) {
|
|
1167
|
+
throw new Error(`Branch name cannot start or end with '/' or '.': ${branch}`);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1137
1170
|
var execAsync = promisify(exec);
|
|
1138
1171
|
async function execGit(command, options = {}) {
|
|
1139
1172
|
const cwd = options.cwd || process.cwd();
|
|
@@ -1281,6 +1314,84 @@ async function getDefaultBranch(options = {}) {
|
|
|
1281
1314
|
}
|
|
1282
1315
|
return "master";
|
|
1283
1316
|
}
|
|
1317
|
+
function validatePath(path) {
|
|
1318
|
+
if (!path || path.trim().length === 0) {
|
|
1319
|
+
throw new Error("Path cannot be empty");
|
|
1320
|
+
}
|
|
1321
|
+
const dangerousChars = /[`$;|&<>(){}[\]'"\n\r]/;
|
|
1322
|
+
if (dangerousChars.test(path)) {
|
|
1323
|
+
throw new Error(`Path contains invalid characters: ${path}`);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
async function createWorktree(worktreePath, branch, options = {}) {
|
|
1327
|
+
validateBranchName(branch);
|
|
1328
|
+
validatePath(worktreePath);
|
|
1329
|
+
const localExists = await branchExists(branch, options);
|
|
1330
|
+
if (localExists) {
|
|
1331
|
+
await execGit(`git worktree add "${worktreePath}" "${branch}"`, options);
|
|
1332
|
+
} else {
|
|
1333
|
+
try {
|
|
1334
|
+
await execGit(`git worktree add "${worktreePath}" -b "${branch}" "origin/${branch}"`, options);
|
|
1335
|
+
} catch {
|
|
1336
|
+
await execGit(`git worktree add -b "${branch}" "${worktreePath}"`, options);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
async function removeWorktree(worktreePath, options = {}, force = false) {
|
|
1341
|
+
validatePath(worktreePath);
|
|
1342
|
+
const forceFlag = force ? "--force" : "";
|
|
1343
|
+
await execGit(`git worktree remove ${forceFlag} "${worktreePath}"`, options);
|
|
1344
|
+
}
|
|
1345
|
+
async function listWorktrees(options = {}) {
|
|
1346
|
+
try {
|
|
1347
|
+
const { stdout } = await execGit("git worktree list --porcelain", options);
|
|
1348
|
+
const worktrees = [];
|
|
1349
|
+
const entries = stdout.trim().split("\n\n");
|
|
1350
|
+
for (const entry of entries) {
|
|
1351
|
+
if (!entry.trim()) continue;
|
|
1352
|
+
const lines = entry.split("\n");
|
|
1353
|
+
const info = {
|
|
1354
|
+
isMain: false,
|
|
1355
|
+
branch: null
|
|
1356
|
+
};
|
|
1357
|
+
for (const line of lines) {
|
|
1358
|
+
if (line.startsWith("worktree ")) {
|
|
1359
|
+
info.path = line.substring(9);
|
|
1360
|
+
} else if (line.startsWith("HEAD ")) {
|
|
1361
|
+
info.head = line.substring(5);
|
|
1362
|
+
} else if (line.startsWith("branch ")) {
|
|
1363
|
+
info.branch = line.substring(7).replace(/^refs\/heads\//, "");
|
|
1364
|
+
} else if (line === "bare") {
|
|
1365
|
+
info.isMain = true;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (worktrees.length === 0) {
|
|
1369
|
+
info.isMain = true;
|
|
1370
|
+
}
|
|
1371
|
+
if (info.path && info.head) {
|
|
1372
|
+
worktrees.push(info);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return worktrees;
|
|
1376
|
+
} catch {
|
|
1377
|
+
return [];
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
async function getWorktreeForBranch(branch, options = {}) {
|
|
1381
|
+
const worktrees = await listWorktrees(options);
|
|
1382
|
+
return worktrees.find((wt) => wt.branch === branch) || null;
|
|
1383
|
+
}
|
|
1384
|
+
async function worktreeExists(worktreePath, options = {}) {
|
|
1385
|
+
const worktrees = await listWorktrees(options);
|
|
1386
|
+
return worktrees.some((wt) => wt.path === worktreePath);
|
|
1387
|
+
}
|
|
1388
|
+
function generateWorktreePath(basePath, repoName, identifier) {
|
|
1389
|
+
const safeRepoName = sanitizeForPath(repoName);
|
|
1390
|
+
const safeIdentifier = sanitizeForPath(String(identifier));
|
|
1391
|
+
const expandedBase = basePath.startsWith("~") ? basePath.replace("~", homedir()) : basePath;
|
|
1392
|
+
const cleanBase = expandedBase.replace(/\/+$/, "");
|
|
1393
|
+
return `${cleanBase}/${safeRepoName}/${safeIdentifier}`;
|
|
1394
|
+
}
|
|
1284
1395
|
|
|
1285
1396
|
// src/sync.ts
|
|
1286
1397
|
var SYNCABLE_KEYS = [
|
|
@@ -1460,10 +1571,12 @@ export {
|
|
|
1460
1571
|
checkoutBranch,
|
|
1461
1572
|
computeSettingsDiff,
|
|
1462
1573
|
createBranch,
|
|
1574
|
+
createWorktree,
|
|
1463
1575
|
detectRepository,
|
|
1464
1576
|
fetchOrigin,
|
|
1465
1577
|
formatConflict,
|
|
1466
1578
|
generateBranchName,
|
|
1579
|
+
generateWorktreePath,
|
|
1467
1580
|
getAllBranches,
|
|
1468
1581
|
getCommitsAhead,
|
|
1469
1582
|
getCommitsBehind,
|
|
@@ -1473,9 +1586,11 @@ export {
|
|
|
1473
1586
|
getLocalBranches,
|
|
1474
1587
|
getRemoteBranches,
|
|
1475
1588
|
getRepositoryRoot,
|
|
1589
|
+
getWorktreeForBranch,
|
|
1476
1590
|
hasDifferences,
|
|
1477
1591
|
hasUncommittedChanges,
|
|
1478
1592
|
isGitRepository,
|
|
1593
|
+
listWorktrees,
|
|
1479
1594
|
normalizeVSCodeSettings,
|
|
1480
1595
|
parseBranchLink,
|
|
1481
1596
|
parseGitHubUrl,
|
|
@@ -1483,6 +1598,7 @@ export {
|
|
|
1483
1598
|
pullLatest,
|
|
1484
1599
|
queries_exports as queries,
|
|
1485
1600
|
removeBranchLinkFromBody,
|
|
1601
|
+
removeWorktree,
|
|
1486
1602
|
resolveConflicts,
|
|
1487
1603
|
sanitizeForBranchName,
|
|
1488
1604
|
setBranchLinkInBody,
|
|
@@ -1490,5 +1606,6 @@ export {
|
|
|
1490
1606
|
toVSCodeSettings,
|
|
1491
1607
|
useCli,
|
|
1492
1608
|
useCustom,
|
|
1493
|
-
useVSCode
|
|
1609
|
+
useVSCode,
|
|
1610
|
+
worktreeExists
|
|
1494
1611
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bretwardjames/ghp-core",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.0-beta.1",
|
|
4
4
|
"description": "Shared core library for GitHub Projects tools",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -16,12 +16,6 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
21
|
-
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
22
|
-
"typecheck": "tsc --noEmit",
|
|
23
|
-
"prepublishOnly": "npm run build"
|
|
24
|
-
},
|
|
25
19
|
"keywords": [
|
|
26
20
|
"github",
|
|
27
21
|
"projects",
|
|
@@ -44,5 +38,10 @@
|
|
|
44
38
|
"@types/node": "^20.10.0",
|
|
45
39
|
"tsup": "^8.0.0",
|
|
46
40
|
"typescript": "^5.3.2"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
44
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
47
46
|
}
|
|
48
|
-
}
|
|
47
|
+
}
|