@bretwardjames/ghp-cli 0.1.1 → 0.1.3
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/branch-linker.d.ts +31 -9
- package/dist/branch-linker.d.ts.map +1 -1
- package/dist/branch-linker.js +61 -35
- package/dist/branch-linker.js.map +1 -1
- package/dist/commands/link-branch.d.ts.map +1 -1
- package/dist/commands/link-branch.js +20 -0
- package/dist/commands/link-branch.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +235 -95
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/set-field.d.ts.map +1 -1
- package/dist/commands/set-field.js +25 -1
- package/dist/commands/set-field.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +21 -0
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/switch.d.ts.map +1 -1
- package/dist/commands/switch.js +20 -0
- package/dist/commands/switch.js.map +1 -1
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +77 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/work.d.ts +5 -0
- package/dist/commands/work.d.ts.map +1 -1
- package/dist/commands/work.js +226 -36
- package/dist/commands/work.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/git-utils.d.ts +6 -52
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +7 -137
- package/dist/git-utils.js.map +1 -1
- package/dist/github-api.d.ts +17 -108
- package/dist/github-api.d.ts.map +1 -1
- package/dist/github-api.js +48 -579
- package/dist/github-api.js.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/table.d.ts +16 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +230 -0
- package/dist/table.js.map +1 -0
- package/dist/types.d.ts +6 -36
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
package/dist/branch-linker.d.ts
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* CLI-specific branch linker with file-based storage.
|
|
3
|
+
*
|
|
4
|
+
* This module wraps the core BranchLinker with a file-based StorageAdapter
|
|
5
|
+
* that stores links in ~/.config/ghp-cli/branch-links.json
|
|
6
|
+
*/
|
|
7
|
+
import { BranchLinker, type StorageAdapter, type BranchLink } from '@bretwardjames/ghp-core';
|
|
8
|
+
/**
|
|
9
|
+
* File-based storage adapter for CLI usage
|
|
10
|
+
*/
|
|
11
|
+
declare const fileStorageAdapter: StorageAdapter;
|
|
12
|
+
declare const linker: BranchLinker;
|
|
13
|
+
/**
|
|
14
|
+
* Create a link between a branch and an issue.
|
|
15
|
+
* Backwards-compatible function signature for existing CLI code.
|
|
16
|
+
*/
|
|
9
17
|
export declare function linkBranch(branch: string, issueNumber: number, issueTitle: string, itemId: string, repo: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Remove the link for an issue.
|
|
20
|
+
* @returns true if a link was removed, false if no link existed
|
|
21
|
+
*/
|
|
10
22
|
export declare function unlinkBranch(repo: string, issueNumber: number): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Get the branch linked to an issue.
|
|
25
|
+
*/
|
|
11
26
|
export declare function getBranchForIssue(repo: string, issueNumber: number): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Get the full link info for a branch.
|
|
29
|
+
*/
|
|
12
30
|
export declare function getIssueForBranch(repo: string, branch: string): BranchLink | null;
|
|
31
|
+
/**
|
|
32
|
+
* Get all links for a repository.
|
|
33
|
+
*/
|
|
13
34
|
export declare function getAllLinksForRepo(repo: string): BranchLink[];
|
|
14
|
-
export {};
|
|
35
|
+
export type { BranchLink } from '@bretwardjames/ghp-core';
|
|
36
|
+
export { linker, fileStorageAdapter };
|
|
15
37
|
//# sourceMappingURL=branch-linker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-linker.d.ts","sourceRoot":"","sources":["../src/branch-linker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"branch-linker.d.ts","sourceRoot":"","sources":["../src/branch-linker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EACH,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,UAAU,EAClB,MAAM,yBAAyB,CAAC;AAKjC;;GAEG;AACH,QAAA,MAAM,kBAAkB,EAAE,cAmBzB,CAAC;AAGF,QAAA,MAAM,MAAM,cAAuC,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,UAAU,CACtB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACb,IAAI,CAIN;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAcvE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIlF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAGjF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAG7D;AAGD,YAAY,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAG1D,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC"}
|
package/dist/branch-linker.js
CHANGED
|
@@ -1,60 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-specific branch linker with file-based storage.
|
|
3
|
+
*
|
|
4
|
+
* This module wraps the core BranchLinker with a file-based StorageAdapter
|
|
5
|
+
* that stores links in ~/.config/ghp-cli/branch-links.json
|
|
6
|
+
*/
|
|
1
7
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
8
|
import { homedir } from 'os';
|
|
3
9
|
import { join } from 'path';
|
|
10
|
+
import { BranchLinker, } from '@bretwardjames/ghp-core';
|
|
4
11
|
const DATA_DIR = join(homedir(), '.config', 'ghp-cli');
|
|
5
12
|
const LINKS_FILE = join(DATA_DIR, 'branch-links.json');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
/**
|
|
14
|
+
* File-based storage adapter for CLI usage
|
|
15
|
+
*/
|
|
16
|
+
const fileStorageAdapter = {
|
|
17
|
+
load() {
|
|
18
|
+
try {
|
|
19
|
+
if (existsSync(LINKS_FILE)) {
|
|
20
|
+
const data = readFileSync(LINKS_FILE, 'utf-8');
|
|
21
|
+
return JSON.parse(data);
|
|
22
|
+
}
|
|
11
23
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
+
catch {
|
|
25
|
+
// Ignore errors, return empty array
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
},
|
|
29
|
+
save(links) {
|
|
30
|
+
if (!existsSync(DATA_DIR)) {
|
|
31
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
writeFileSync(LINKS_FILE, JSON.stringify(links, null, 2));
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
// Create singleton linker instance
|
|
37
|
+
const linker = new BranchLinker(fileStorageAdapter);
|
|
38
|
+
/**
|
|
39
|
+
* Create a link between a branch and an issue.
|
|
40
|
+
* Backwards-compatible function signature for existing CLI code.
|
|
41
|
+
*/
|
|
24
42
|
export function linkBranch(branch, issueNumber, issueTitle, itemId, repo) {
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
filtered.push({
|
|
29
|
-
branch,
|
|
30
|
-
issueNumber,
|
|
31
|
-
issueTitle,
|
|
32
|
-
itemId,
|
|
33
|
-
repo,
|
|
34
|
-
linkedAt: new Date().toISOString(),
|
|
35
|
-
});
|
|
36
|
-
saveLinks(filtered);
|
|
43
|
+
// Use sync-like behavior for backwards compatibility
|
|
44
|
+
// The file adapter is synchronous so this works
|
|
45
|
+
linker.link(branch, issueNumber, issueTitle, itemId, repo);
|
|
37
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Remove the link for an issue.
|
|
49
|
+
* @returns true if a link was removed, false if no link existed
|
|
50
|
+
*/
|
|
38
51
|
export function unlinkBranch(repo, issueNumber) {
|
|
39
|
-
|
|
52
|
+
// Load, filter, and save synchronously for backwards compatibility
|
|
53
|
+
const adapter = fileStorageAdapter;
|
|
54
|
+
const links = adapter.load();
|
|
40
55
|
const filtered = links.filter(l => !(l.repo === repo && l.issueNumber === issueNumber));
|
|
41
56
|
if (filtered.length === links.length) {
|
|
42
|
-
return false;
|
|
57
|
+
return false;
|
|
43
58
|
}
|
|
44
|
-
|
|
59
|
+
adapter.save(filtered);
|
|
45
60
|
return true;
|
|
46
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the branch linked to an issue.
|
|
64
|
+
*/
|
|
47
65
|
export function getBranchForIssue(repo, issueNumber) {
|
|
48
|
-
const links =
|
|
66
|
+
const links = fileStorageAdapter.load();
|
|
49
67
|
const link = links.find(l => l.repo === repo && l.issueNumber === issueNumber);
|
|
50
68
|
return link?.branch || null;
|
|
51
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the full link info for a branch.
|
|
72
|
+
*/
|
|
52
73
|
export function getIssueForBranch(repo, branch) {
|
|
53
|
-
const links =
|
|
74
|
+
const links = fileStorageAdapter.load();
|
|
54
75
|
return links.find(l => l.repo === repo && l.branch === branch) || null;
|
|
55
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Get all links for a repository.
|
|
79
|
+
*/
|
|
56
80
|
export function getAllLinksForRepo(repo) {
|
|
57
|
-
const links =
|
|
81
|
+
const links = fileStorageAdapter.load();
|
|
58
82
|
return links.filter(l => l.repo === repo);
|
|
59
83
|
}
|
|
84
|
+
// Also export the linker instance and adapter for advanced usage
|
|
85
|
+
export { linker, fileStorageAdapter };
|
|
60
86
|
//# sourceMappingURL=branch-linker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-linker.js","sourceRoot":"","sources":["../src/branch-linker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"branch-linker.js","sourceRoot":"","sources":["../src/branch-linker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-branch.d.ts","sourceRoot":"","sources":["../../src/commands/link-branch.ts"],"names":[],"mappings":"AAKA,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"link-branch.d.ts","sourceRoot":"","sources":["../../src/commands/link-branch.ts"],"names":[],"mappings":"AAKA,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoErF"}
|
|
@@ -40,5 +40,25 @@ export async function linkBranchCommand(issue, branch) {
|
|
|
40
40
|
// Create link
|
|
41
41
|
linkBranch(branchName, issueNumber, item.title, item.id, repo.fullName);
|
|
42
42
|
console.log(chalk.green('✓'), `Linked "${branchName}" to #${issueNumber}: ${item.title}`);
|
|
43
|
+
// If this branch is currently checked out, apply active label
|
|
44
|
+
const currentBranch = await getCurrentBranch();
|
|
45
|
+
if (currentBranch === branchName) {
|
|
46
|
+
const activeLabel = api.getActiveLabelName();
|
|
47
|
+
// Ensure the label exists
|
|
48
|
+
await api.ensureLabel(repo, activeLabel);
|
|
49
|
+
// Remove label from any other issues that have it
|
|
50
|
+
const issuesWithLabel = await api.findIssuesWithLabel(repo, activeLabel);
|
|
51
|
+
for (const otherIssue of issuesWithLabel) {
|
|
52
|
+
if (otherIssue !== issueNumber) {
|
|
53
|
+
await api.removeLabelFromIssue(repo, otherIssue, activeLabel);
|
|
54
|
+
console.log(chalk.dim(`Removed ${activeLabel} from #${otherIssue}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Add label to current issue
|
|
58
|
+
const labelAdded = await api.addLabelToIssue(repo, issueNumber, activeLabel);
|
|
59
|
+
if (labelAdded) {
|
|
60
|
+
console.log(chalk.green('✓'), `Applied "${activeLabel}" label (branch is checked out)`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
43
63
|
}
|
|
44
64
|
//# sourceMappingURL=link-branch.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-branch.js","sourceRoot":"","sources":["../../src/commands/link-branch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAEpE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAa,EAAE,MAAe;IAClE,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACxC,IAAI,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,wBAAwB,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,oBAAoB;IACpB,MAAM,IAAI,GAAG,MAAM,gBAAgB,EAAE,CAAC;IACtC,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,8CAA8C,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,8CAA8C;IAC9C,MAAM,UAAU,GAAG,MAAM,IAAI,MAAM,gBAAgB,EAAE,CAAC;IACtD,IAAI,CAAC,UAAU,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,oDAAoD,CAAC,CAAC;QACzF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,eAAe;IACf,MAAM,aAAa,GAAG,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC;IAC/C,IAAI,CAAC,aAAa,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,wBAAwB,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACrF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,iBAAiB;IACjB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,gBAAgB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,WAAW,2BAA2B,CAAC,CAAC;QACrF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,0BAA0B;IAC1B,MAAM,cAAc,GAAG,iBAAiB,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACrE,IAAI,cAAc,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,UAAU,WAAW,mBAAmB,cAAc,GAAG,CAAC,CAAC;IAClG,CAAC;IAED,cAAc;IACd,UAAU,CAAC,UAAU,EAAE,WAAW,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,WAAW,UAAU,SAAS,WAAW,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"link-branch.js","sourceRoot":"","sources":["../../src/commands/link-branch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAEpE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAa,EAAE,MAAe;IAClE,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACxC,IAAI,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,wBAAwB,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,oBAAoB;IACpB,MAAM,IAAI,GAAG,MAAM,gBAAgB,EAAE,CAAC;IACtC,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,8CAA8C,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,8CAA8C;IAC9C,MAAM,UAAU,GAAG,MAAM,IAAI,MAAM,gBAAgB,EAAE,CAAC;IACtD,IAAI,CAAC,UAAU,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,oDAAoD,CAAC,CAAC;QACzF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,eAAe;IACf,MAAM,aAAa,GAAG,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC;IAC/C,IAAI,CAAC,aAAa,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,wBAAwB,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACrF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,iBAAiB;IACjB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,gBAAgB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;QACR,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,UAAU,WAAW,2BAA2B,CAAC,CAAC;QACrF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,0BAA0B;IAC1B,MAAM,cAAc,GAAG,iBAAiB,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACrE,IAAI,cAAc,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,UAAU,WAAW,mBAAmB,cAAc,GAAG,CAAC,CAAC;IAClG,CAAC;IAED,cAAc;IACd,UAAU,CAAC,UAAU,EAAE,WAAW,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,WAAW,UAAU,SAAS,WAAW,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAE1F,8DAA8D;IAC9D,MAAM,aAAa,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAC/C,IAAI,aAAa,KAAK,UAAU,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAG,GAAG,CAAC,kBAAkB,EAAE,CAAC;QAE7C,0BAA0B;QAC1B,MAAM,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAEzC,kDAAkD;QAClD,MAAM,eAAe,GAAG,MAAM,GAAG,CAAC,mBAAmB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QACzE,KAAK,MAAM,UAAU,IAAI,eAAe,EAAE,CAAC;YACvC,IAAI,UAAU,KAAK,WAAW,EAAE,CAAC;gBAC7B,MAAM,GAAG,CAAC,oBAAoB,CAAC,IAAI,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;gBAC9D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,WAAW,UAAU,UAAU,EAAE,CAAC,CAAC,CAAC;YACzE,CAAC;QACL,CAAC;QAED,6BAA6B;QAC7B,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;QAC7E,IAAI,UAAU,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,YAAY,WAAW,iCAAiC,CAAC,CAAC;QAC5F,CAAC;IACL,CAAC;AACL,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/commands/plan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/commands/plan.ts"],"names":[],"mappings":"AAyGA,wBAAsB,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CA2QjF"}
|
package/dist/commands/plan.js
CHANGED
|
@@ -1,7 +1,71 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { api } from '../github-api.js';
|
|
3
3
|
import { detectRepository } from '../git-utils.js';
|
|
4
|
-
import { getShortcut, getPlanDefaults, listShortcuts } from '../config.js';
|
|
4
|
+
import { getShortcut, getPlanDefaults, listShortcuts, getConfig } from '../config.js';
|
|
5
|
+
import { displayTable, parseColumns, DEFAULT_COLUMNS, calculateColumnWidths, displayTableWithWidths } from '../table.js';
|
|
6
|
+
/**
|
|
7
|
+
* Parse and apply a GitHub Project view filter expression
|
|
8
|
+
* Format: field:value1,value2 field2:value3 -field3:excluded
|
|
9
|
+
* Special: @me expands to current username
|
|
10
|
+
*/
|
|
11
|
+
function applyViewFilter(items, filterExpr, username) {
|
|
12
|
+
if (!filterExpr.trim())
|
|
13
|
+
return items;
|
|
14
|
+
// Parse filter tokens: field:values or -field:values (negation)
|
|
15
|
+
const tokens = filterExpr.match(/(-?\w+):"([^"]+)"|(-?\w+):(\S+)/g) || [];
|
|
16
|
+
let result = items;
|
|
17
|
+
for (const token of tokens) {
|
|
18
|
+
const negated = token.startsWith('-');
|
|
19
|
+
const cleanToken = negated ? token.slice(1) : token;
|
|
20
|
+
// Parse field:values
|
|
21
|
+
const colonIdx = cleanToken.indexOf(':');
|
|
22
|
+
if (colonIdx === -1)
|
|
23
|
+
continue;
|
|
24
|
+
const field = cleanToken.slice(0, colonIdx).toLowerCase();
|
|
25
|
+
let valuesStr = cleanToken.slice(colonIdx + 1);
|
|
26
|
+
// Remove quotes if present
|
|
27
|
+
if (valuesStr.startsWith('"') && valuesStr.endsWith('"')) {
|
|
28
|
+
valuesStr = valuesStr.slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
// Split values by comma and expand @me
|
|
31
|
+
const values = valuesStr.split(',').map(v => {
|
|
32
|
+
const trimmed = v.trim();
|
|
33
|
+
return trimmed === '@me' ? username : trimmed;
|
|
34
|
+
});
|
|
35
|
+
result = result.filter(item => {
|
|
36
|
+
let matches = false;
|
|
37
|
+
switch (field) {
|
|
38
|
+
case 'status':
|
|
39
|
+
matches = values.some(v => item.status?.toLowerCase() === v.toLowerCase());
|
|
40
|
+
break;
|
|
41
|
+
case 'assignee':
|
|
42
|
+
case 'assignees':
|
|
43
|
+
matches = values.some(v => item.assignees.some(a => a.toLowerCase() === v.toLowerCase()));
|
|
44
|
+
break;
|
|
45
|
+
case 'label':
|
|
46
|
+
case 'labels':
|
|
47
|
+
matches = values.some(v => item.labels.some(l => l.name.toLowerCase().includes(v.toLowerCase())));
|
|
48
|
+
break;
|
|
49
|
+
case 'type':
|
|
50
|
+
matches = values.some(v => item.issueType?.toLowerCase() === v.toLowerCase());
|
|
51
|
+
break;
|
|
52
|
+
case 'repo':
|
|
53
|
+
case 'repository':
|
|
54
|
+
matches = values.some(v => item.repository?.toLowerCase().includes(v.toLowerCase()));
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
// Check custom fields
|
|
58
|
+
const fieldValue = item.fields[field] ||
|
|
59
|
+
Object.entries(item.fields).find(([k]) => k.toLowerCase() === field)?.[1];
|
|
60
|
+
if (fieldValue) {
|
|
61
|
+
matches = values.some(v => fieldValue.toLowerCase().includes(v.toLowerCase()));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return negated ? !matches : matches;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
5
69
|
export async function planCommand(shortcut, command) {
|
|
6
70
|
// Commander passes (shortcut, options) for optional positional args
|
|
7
71
|
// The options object is passed directly, not as command.opts()
|
|
@@ -93,6 +157,36 @@ export async function planCommand(shortcut, command) {
|
|
|
93
157
|
}
|
|
94
158
|
// Apply filters
|
|
95
159
|
let filteredItems = allItems;
|
|
160
|
+
// --view: filter by project view's filter expression
|
|
161
|
+
if (options.view) {
|
|
162
|
+
// Find the view in target projects
|
|
163
|
+
let viewFound = false;
|
|
164
|
+
for (const project of targetProjects) {
|
|
165
|
+
const views = await api.getProjectViews(project.id);
|
|
166
|
+
const view = views.find(v => v.name.toLowerCase() === options.view.toLowerCase());
|
|
167
|
+
if (view) {
|
|
168
|
+
viewFound = true;
|
|
169
|
+
if (view.filter) {
|
|
170
|
+
// Parse and apply the view's filter
|
|
171
|
+
filteredItems = applyViewFilter(filteredItems, view.filter, api.username || '');
|
|
172
|
+
}
|
|
173
|
+
// Only use items from this project for the view
|
|
174
|
+
filteredItems = filteredItems.filter(item => item.projectId === project.id);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!viewFound) {
|
|
179
|
+
console.error(chalk.red('View not found:'), options.view);
|
|
180
|
+
console.log(chalk.dim('Available views:'));
|
|
181
|
+
for (const project of targetProjects) {
|
|
182
|
+
const views = await api.getProjectViews(project.id);
|
|
183
|
+
for (const v of views) {
|
|
184
|
+
console.log(` ${chalk.cyan(v.name)}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
96
190
|
// --mine: filter to current user
|
|
97
191
|
if (options.mine) {
|
|
98
192
|
filteredItems = filteredItems.filter(item => item.assignees.includes(api.username || ''));
|
|
@@ -133,6 +227,10 @@ export async function planCommand(shortcut, command) {
|
|
|
133
227
|
if (fieldLower === 'project') {
|
|
134
228
|
return item.projectTitle.toLowerCase().includes(valueLower);
|
|
135
229
|
}
|
|
230
|
+
if (fieldLower === 'state') {
|
|
231
|
+
// state can be: open, closed, merged
|
|
232
|
+
return item.state?.toLowerCase() === valueLower;
|
|
233
|
+
}
|
|
136
234
|
// Check custom project fields
|
|
137
235
|
const fieldValue = item.fields[field] || item.fields[fieldLower];
|
|
138
236
|
if (fieldValue) {
|
|
@@ -178,14 +276,39 @@ export async function planCommand(shortcut, command) {
|
|
|
178
276
|
return 0;
|
|
179
277
|
});
|
|
180
278
|
}
|
|
279
|
+
// Determine if we need status column (when items have mixed statuses)
|
|
280
|
+
const needsStatusColumn = options.list || options.all || options.view || (options.group && options.group.toLowerCase() !== 'status');
|
|
281
|
+
const defaultColumnsWithStatus = ['number', 'type', 'title', 'status', 'assignees', 'priority', 'size', 'labels'];
|
|
181
282
|
// Display based on mode
|
|
182
|
-
if (options.
|
|
183
|
-
//
|
|
184
|
-
|
|
283
|
+
if (options.group) {
|
|
284
|
+
// Grouped view - group by specified field
|
|
285
|
+
displayGroupedView(filteredItems, options.group, options);
|
|
286
|
+
}
|
|
287
|
+
else if (options.list || options.all || options.view) {
|
|
288
|
+
// Table view for all items or view items (includes status column)
|
|
289
|
+
const label = options.view
|
|
290
|
+
? `View: ${options.view}`
|
|
291
|
+
: options.mine
|
|
292
|
+
? 'My Items'
|
|
293
|
+
: options.unassigned
|
|
294
|
+
? 'Unassigned Items'
|
|
295
|
+
: 'All Items';
|
|
296
|
+
console.log(chalk.bold(label), chalk.dim(`(${filteredItems.length} items)`));
|
|
297
|
+
console.log();
|
|
298
|
+
const columnsConfig = getConfig('columns');
|
|
299
|
+
const columns = columnsConfig ? parseColumns(columnsConfig) : defaultColumnsWithStatus;
|
|
300
|
+
displayTable(filteredItems, columns);
|
|
301
|
+
console.log();
|
|
185
302
|
}
|
|
186
303
|
else if (options.status) {
|
|
187
|
-
//
|
|
188
|
-
|
|
304
|
+
// Table view for single status
|
|
305
|
+
const label = options.mine ? `My ${options.status}` : options.unassigned ? `Unassigned ${options.status}` : options.status;
|
|
306
|
+
console.log(chalk.bold(label), chalk.dim(`(${filteredItems.length} items)`));
|
|
307
|
+
console.log();
|
|
308
|
+
const columnsConfig = getConfig('columns');
|
|
309
|
+
const columns = columnsConfig ? parseColumns(columnsConfig) : DEFAULT_COLUMNS;
|
|
310
|
+
displayTable(filteredItems, columns);
|
|
311
|
+
console.log();
|
|
189
312
|
}
|
|
190
313
|
else {
|
|
191
314
|
// Board view
|
|
@@ -201,7 +324,8 @@ function getFieldValue(item, fieldName) {
|
|
|
201
324
|
case 'title':
|
|
202
325
|
return item.title;
|
|
203
326
|
case 'status':
|
|
204
|
-
|
|
327
|
+
// Return statusIndex for sorting by project's defined order
|
|
328
|
+
return item.statusIndex;
|
|
205
329
|
case 'type':
|
|
206
330
|
return item.type;
|
|
207
331
|
case 'issuetype':
|
|
@@ -238,103 +362,119 @@ function displaySimpleList(items) {
|
|
|
238
362
|
console.log(`${num} ${item.title} ${status}`);
|
|
239
363
|
}
|
|
240
364
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
365
|
+
/**
|
|
366
|
+
* Get display value for a field (for grouping headers, not sorting)
|
|
367
|
+
*/
|
|
368
|
+
function getFieldDisplayValue(item, fieldName) {
|
|
369
|
+
const lower = fieldName.toLowerCase();
|
|
370
|
+
switch (lower) {
|
|
371
|
+
case 'status':
|
|
372
|
+
return item.status || 'No Status';
|
|
373
|
+
case 'type':
|
|
374
|
+
case 'issuetype':
|
|
375
|
+
case 'issue-type':
|
|
376
|
+
return item.issueType || 'No Type';
|
|
377
|
+
case 'assignee':
|
|
378
|
+
case 'assignees':
|
|
379
|
+
return item.assignees.length > 0 ? item.assignees.join(', ') : 'Unassigned';
|
|
380
|
+
case 'priority':
|
|
381
|
+
return item.fields['Priority'] || item.fields['priority'] || 'No Priority';
|
|
382
|
+
case 'size':
|
|
383
|
+
return item.fields['Size'] || item.fields['size'] || item.fields['Estimate'] || 'No Size';
|
|
384
|
+
case 'label':
|
|
385
|
+
case 'labels':
|
|
386
|
+
return item.labels.length > 0 ? item.labels.map(l => l.name).join(', ') : 'No Labels';
|
|
387
|
+
case 'project':
|
|
388
|
+
return item.projectTitle;
|
|
389
|
+
case 'repo':
|
|
390
|
+
case 'repository':
|
|
391
|
+
return item.repository || 'Unknown';
|
|
392
|
+
default:
|
|
393
|
+
// Check custom fields
|
|
394
|
+
const fieldValue = item.fields[fieldName] ||
|
|
395
|
+
Object.entries(item.fields).find(([k]) => k.toLowerCase() === lower)?.[1];
|
|
396
|
+
return fieldValue || `No ${fieldName}`;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Display items grouped by a field
|
|
401
|
+
*/
|
|
402
|
+
function displayGroupedView(items, groupField, opts) {
|
|
245
403
|
if (items.length === 0) {
|
|
246
404
|
console.log(chalk.dim('No items found.'));
|
|
247
405
|
return;
|
|
248
406
|
}
|
|
249
|
-
//
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// Calculate column widths
|
|
260
|
-
const numWidth = Math.max(...rows.map(r => r.num.length), 5);
|
|
261
|
-
const typeWidth = Math.max(...rows.map(r => r.type.length), 0);
|
|
262
|
-
const assigneeWidth = Math.max(...rows.map(r => r.assignees.length), 0);
|
|
263
|
-
const priorityWidth = Math.max(...rows.map(r => r.priority.length), 0);
|
|
264
|
-
const sizeWidth = Math.max(...rows.map(r => r.size.length), 0);
|
|
265
|
-
// Calculate title width (remaining space, min 20, max 60)
|
|
266
|
-
const termWidth = process.stdout.columns || 120;
|
|
267
|
-
const fixedWidth = numWidth + typeWidth + assigneeWidth + priorityWidth + sizeWidth + 12; // spacing
|
|
268
|
-
const titleWidth = Math.max(20, Math.min(60, termWidth - fixedWidth - 4));
|
|
269
|
-
// Print header row
|
|
270
|
-
const headerParts = [];
|
|
271
|
-
headerParts.push(chalk.dim('#'.padEnd(numWidth)));
|
|
272
|
-
if (typeWidth > 0) {
|
|
273
|
-
headerParts.push(chalk.dim('Type'.padEnd(typeWidth)));
|
|
274
|
-
}
|
|
275
|
-
headerParts.push(chalk.dim('Title'.padEnd(titleWidth)));
|
|
276
|
-
if (assigneeWidth > 0) {
|
|
277
|
-
headerParts.push(chalk.dim('Assignee'.padEnd(assigneeWidth)));
|
|
407
|
+
// Group items by the field value
|
|
408
|
+
const groups = new Map();
|
|
409
|
+
const groupOrder = []; // Track order of first appearance
|
|
410
|
+
for (const item of items) {
|
|
411
|
+
const groupValue = getFieldDisplayValue(item, groupField);
|
|
412
|
+
if (!groups.has(groupValue)) {
|
|
413
|
+
groups.set(groupValue, []);
|
|
414
|
+
groupOrder.push(groupValue);
|
|
415
|
+
}
|
|
416
|
+
groups.get(groupValue).push(item);
|
|
278
417
|
}
|
|
279
|
-
|
|
280
|
-
|
|
418
|
+
// Sort groups by the field's natural order (using getFieldValue for sort key)
|
|
419
|
+
// For status, this will use statusIndex; for others, alphabetical
|
|
420
|
+
const lower = groupField.toLowerCase();
|
|
421
|
+
if (lower === 'status') {
|
|
422
|
+
// Sort by statusIndex of first item in each group
|
|
423
|
+
groupOrder.sort((a, b) => {
|
|
424
|
+
const aItem = groups.get(a)[0];
|
|
425
|
+
const bItem = groups.get(b)[0];
|
|
426
|
+
return aItem.statusIndex - bItem.statusIndex;
|
|
427
|
+
});
|
|
281
428
|
}
|
|
282
|
-
|
|
283
|
-
|
|
429
|
+
else {
|
|
430
|
+
// Alphabetical, but put "No X" / "Unassigned" at the end
|
|
431
|
+
groupOrder.sort((a, b) => {
|
|
432
|
+
const aIsEmpty = a.startsWith('No ') || a === 'Unassigned';
|
|
433
|
+
const bIsEmpty = b.startsWith('No ') || b === 'Unassigned';
|
|
434
|
+
if (aIsEmpty && !bIsEmpty)
|
|
435
|
+
return 1;
|
|
436
|
+
if (!aIsEmpty && bIsEmpty)
|
|
437
|
+
return -1;
|
|
438
|
+
return a.localeCompare(b);
|
|
439
|
+
});
|
|
284
440
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
441
|
+
// Determine columns - exclude the group field from columns
|
|
442
|
+
const columnsConfig = getConfig('columns');
|
|
443
|
+
let columns = columnsConfig
|
|
444
|
+
? parseColumns(columnsConfig)
|
|
445
|
+
: DEFAULT_COLUMNS;
|
|
446
|
+
// Remove the grouped field from columns since it's shown in the header
|
|
447
|
+
const groupFieldLower = groupField.toLowerCase();
|
|
448
|
+
const fieldToColumnMap = {
|
|
449
|
+
'status': 'status',
|
|
450
|
+
'type': 'type',
|
|
451
|
+
'issuetype': 'type',
|
|
452
|
+
'issue-type': 'type',
|
|
453
|
+
'assignee': 'assignees',
|
|
454
|
+
'assignees': 'assignees',
|
|
455
|
+
'priority': 'priority',
|
|
456
|
+
'size': 'size',
|
|
457
|
+
'label': 'labels',
|
|
458
|
+
'labels': 'labels',
|
|
459
|
+
'project': 'project',
|
|
460
|
+
'repo': 'repository',
|
|
461
|
+
'repository': 'repository',
|
|
462
|
+
};
|
|
463
|
+
const columnToRemove = fieldToColumnMap[groupFieldLower];
|
|
464
|
+
if (columnToRemove) {
|
|
465
|
+
columns = columns.filter(c => c !== columnToRemove);
|
|
288
466
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (typeWidth > 0) {
|
|
300
|
-
parts.push(chalk.yellow(row.type.padEnd(typeWidth)));
|
|
301
|
-
}
|
|
302
|
-
// Title (truncated if needed)
|
|
303
|
-
const truncTitle = row.title.length > titleWidth
|
|
304
|
-
? row.title.substring(0, titleWidth - 1) + '…'
|
|
305
|
-
: row.title.padEnd(titleWidth);
|
|
306
|
-
parts.push(truncTitle);
|
|
307
|
-
// Assignees
|
|
308
|
-
if (assigneeWidth > 0) {
|
|
309
|
-
parts.push(chalk.cyan(row.assignees.padEnd(assigneeWidth)));
|
|
310
|
-
}
|
|
311
|
-
// Priority
|
|
312
|
-
if (priorityWidth > 0) {
|
|
313
|
-
parts.push(chalk.magenta(row.priority.padEnd(priorityWidth)));
|
|
314
|
-
}
|
|
315
|
-
// Size
|
|
316
|
-
if (sizeWidth > 0) {
|
|
317
|
-
parts.push(chalk.blue(row.size.padEnd(sizeWidth)));
|
|
318
|
-
}
|
|
319
|
-
// Labels (not padded, at the end)
|
|
320
|
-
if (row.labels.length > 0) {
|
|
321
|
-
const labelStr = row.labels.map(l => {
|
|
322
|
-
const bg = hexToChalk(l.color);
|
|
323
|
-
return bg(` ${l.name} `);
|
|
324
|
-
}).join(' ');
|
|
325
|
-
parts.push(labelStr);
|
|
326
|
-
}
|
|
327
|
-
console.log(` ${parts.join(' ')}`);
|
|
467
|
+
// Pre-calculate column widths based on ALL items so tables align across groups
|
|
468
|
+
// We do this by getting the max width needed for each column across all items
|
|
469
|
+
const columnWidths = calculateColumnWidths(items, columns);
|
|
470
|
+
// Display each group
|
|
471
|
+
for (const groupValue of groupOrder) {
|
|
472
|
+
const groupItems = groups.get(groupValue);
|
|
473
|
+
console.log(chalk.bold.cyan(`■ ${groupValue}`) + chalk.dim(` (${groupItems.length})`));
|
|
474
|
+
console.log();
|
|
475
|
+
displayTableWithWidths(groupItems, columns, columnWidths);
|
|
476
|
+
console.log();
|
|
328
477
|
}
|
|
329
|
-
console.log();
|
|
330
|
-
}
|
|
331
|
-
function hexToChalk(hex) {
|
|
332
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
333
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
334
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
335
|
-
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
336
|
-
const textColor = luminance > 0.5 ? chalk.black : chalk.white;
|
|
337
|
-
return (text) => textColor.bgRgb(r, g, b)(text);
|
|
338
478
|
}
|
|
339
479
|
async function displayBoardView(items, projects, opts) {
|
|
340
480
|
for (const project of projects) {
|