@sascha384/tic 0.1.0 → 1.0.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 +46 -8
- package/dist/backends/ado/az.d.ts +11 -0
- package/dist/backends/ado/az.js +62 -0
- package/dist/backends/ado/az.js.map +1 -0
- package/dist/backends/ado/index.d.ts +28 -0
- package/dist/backends/ado/index.js +457 -0
- package/dist/backends/ado/index.js.map +1 -0
- package/dist/backends/ado/mappers.d.ts +40 -0
- package/dist/backends/ado/mappers.js +92 -0
- package/dist/backends/ado/mappers.js.map +1 -0
- package/dist/backends/ado/remote.d.ts +5 -0
- package/dist/backends/ado/remote.js +37 -0
- package/dist/backends/ado/remote.js.map +1 -0
- package/dist/backends/factory.js +5 -2
- package/dist/backends/factory.js.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,34 +1,46 @@
|
|
|
1
1
|
# tic
|
|
2
2
|
|
|
3
|
-
A terminal UI for issue tracking, built for developers who live in the terminal. Track work items
|
|
3
|
+
A terminal UI for issue tracking, built for developers who live in the terminal. Track work items across multiple backends — local markdown files, GitHub Issues, GitLab Issues, and Azure DevOps Work Items.
|
|
4
4
|
|
|
5
5
|
Built with TypeScript and [Ink](https://github.com/vadimdemedes/ink).
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Keyboard-driven TUI** — browse, create, edit, and manage work items without leaving the terminal
|
|
10
|
+
- **Multiple backends** — local markdown, GitHub (via `gh`), GitLab (via `glab`), Azure DevOps (via `az`)
|
|
11
|
+
- **Automatic backend detection** — selects backend based on git remote, or configure manually
|
|
10
12
|
- **Local markdown storage** — work items stored as markdown files with YAML frontmatter in a `.tic/` directory
|
|
13
|
+
- **CLI commands** — scriptable commands for all operations (`tic item list`, `tic item create`, etc.)
|
|
11
14
|
- **Work item types** — organize by epic, issue, and task (configurable)
|
|
12
15
|
- **Iterations** — group work into sprints or milestones
|
|
13
16
|
- **Parent-child relationships** — build hierarchies with tree-indented views
|
|
14
17
|
- **Dependencies** — track which items block others, with warnings on premature completion
|
|
15
18
|
- **Priority & assignee** — track who owns what and what's most important
|
|
16
19
|
- **Comments** — add timestamped comments to any work item
|
|
20
|
+
- **MCP server** — expose work items to AI assistants via the Model Context Protocol
|
|
17
21
|
|
|
18
22
|
## Installation
|
|
19
23
|
|
|
20
24
|
```bash
|
|
21
|
-
npm install -g tic
|
|
25
|
+
npm install -g @sascha384/tic
|
|
22
26
|
```
|
|
23
27
|
|
|
24
28
|
## Quick Start
|
|
25
29
|
|
|
26
30
|
```bash
|
|
27
31
|
cd your-project
|
|
28
|
-
tic
|
|
32
|
+
tic init # Initialize (auto-detects backend from git remote)
|
|
33
|
+
tic # Launch the TUI
|
|
29
34
|
```
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
For local storage, `tic init` creates a `.tic/` directory to store your work items. For GitHub, GitLab, or Azure DevOps projects, it detects the backend from the git remote automatically. You can also specify a backend explicitly:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
tic init --backend github
|
|
40
|
+
tic init --backend gitlab
|
|
41
|
+
tic init --backend azure
|
|
42
|
+
tic init --backend local
|
|
43
|
+
```
|
|
32
44
|
|
|
33
45
|
## Usage
|
|
34
46
|
|
|
@@ -205,12 +217,38 @@ Deletion is a two-step process: `delete_item` returns a preview of what will be
|
|
|
205
217
|
|
|
206
218
|
If the server is started in a directory without a `.tic/` project, all tools except `init_project` will return an error asking you to initialize first.
|
|
207
219
|
|
|
208
|
-
##
|
|
220
|
+
## CLI Commands
|
|
221
|
+
|
|
222
|
+
tic also provides a full CLI for scripting and automation:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
tic item list # List work items
|
|
226
|
+
tic item list --status in-progress # Filter by status
|
|
227
|
+
tic item list --type task # Filter by type
|
|
228
|
+
tic item show 42 # Show item details
|
|
229
|
+
tic item create --title "Fix bug" # Create an item
|
|
230
|
+
tic item update 42 --status done # Update an item
|
|
231
|
+
tic item delete 42 # Delete an item
|
|
232
|
+
tic item comment 42 --body "Done" # Add a comment
|
|
233
|
+
tic item open 42 # Open in editor/browser
|
|
234
|
+
tic iteration list # List iterations
|
|
235
|
+
tic iteration set sprint-2 # Set current iteration
|
|
236
|
+
tic config get backend # Get config value
|
|
237
|
+
tic config set backend github # Set config value
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Add `--json` to any command for machine-readable output, or `--quiet` to suppress non-essential output.
|
|
241
|
+
|
|
242
|
+
## Backends
|
|
209
243
|
|
|
210
|
-
|
|
244
|
+
| Backend | CLI Tool | Detection |
|
|
245
|
+
|---------|----------|-----------|
|
|
246
|
+
| Local markdown | — | Default fallback |
|
|
247
|
+
| GitHub Issues | [`gh`](https://cli.github.com/) | `github.com` in git remote |
|
|
248
|
+
| GitLab Issues | [`glab`](https://gitlab.com/gitlab-org/cli) | `gitlab.com` in git remote |
|
|
249
|
+
| Azure DevOps Work Items | [`az`](https://learn.microsoft.com/en-us/cli/azure/) | `dev.azure.com` or `visualstudio.com` in git remote |
|
|
211
250
|
|
|
212
|
-
|
|
213
|
-
- **Automatic backend detection** — select backend based on git remote
|
|
251
|
+
Each backend supports a different set of capabilities (types, statuses, iterations, relationships, etc.). The TUI and CLI automatically adapt to show only what the active backend supports.
|
|
214
252
|
|
|
215
253
|
## Contributing
|
|
216
254
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function az<T>(args: string[], cwd: string): T;
|
|
2
|
+
export declare function azExec(args: string[], cwd: string): string;
|
|
3
|
+
export interface AzInvokeOptions {
|
|
4
|
+
area: string;
|
|
5
|
+
resource: string;
|
|
6
|
+
httpMethod?: string;
|
|
7
|
+
routeParameters?: string;
|
|
8
|
+
body?: unknown;
|
|
9
|
+
apiVersion?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function azInvoke<T>(options: AzInvokeOptions, cwd: string): T;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
const AZ_TIMEOUT_MS = 15_000;
|
|
6
|
+
export function az(args, cwd) {
|
|
7
|
+
const result = execFileSync('az', [...args, '-o', 'json'], {
|
|
8
|
+
cwd,
|
|
9
|
+
encoding: 'utf-8',
|
|
10
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
11
|
+
timeout: AZ_TIMEOUT_MS,
|
|
12
|
+
});
|
|
13
|
+
return JSON.parse(result);
|
|
14
|
+
}
|
|
15
|
+
export function azExec(args, cwd) {
|
|
16
|
+
return execFileSync('az', args, {
|
|
17
|
+
cwd,
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
20
|
+
timeout: AZ_TIMEOUT_MS,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export function azInvoke(options, cwd) {
|
|
24
|
+
const args = [
|
|
25
|
+
'devops',
|
|
26
|
+
'invoke',
|
|
27
|
+
'--area',
|
|
28
|
+
options.area,
|
|
29
|
+
'--resource',
|
|
30
|
+
options.resource,
|
|
31
|
+
];
|
|
32
|
+
if (options.httpMethod) {
|
|
33
|
+
args.push('--http-method', options.httpMethod);
|
|
34
|
+
}
|
|
35
|
+
if (options.routeParameters) {
|
|
36
|
+
args.push('--route-parameters', options.routeParameters);
|
|
37
|
+
}
|
|
38
|
+
if (options.apiVersion) {
|
|
39
|
+
args.push('--api-version', options.apiVersion);
|
|
40
|
+
}
|
|
41
|
+
let tmpFile;
|
|
42
|
+
if (options.body) {
|
|
43
|
+
tmpFile = path.join(os.tmpdir(), `tic-az-${Date.now()}.json`);
|
|
44
|
+
fs.writeFileSync(tmpFile, JSON.stringify(options.body));
|
|
45
|
+
args.push('--in-file', tmpFile);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const result = execFileSync('az', [...args, '-o', 'json'], {
|
|
49
|
+
cwd,
|
|
50
|
+
encoding: 'utf-8',
|
|
51
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
52
|
+
timeout: AZ_TIMEOUT_MS,
|
|
53
|
+
});
|
|
54
|
+
return JSON.parse(result);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
if (tmpFile) {
|
|
58
|
+
fs.unlinkSync(tmpFile);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=az.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"az.js","sourceRoot":"","sources":["../../../src/backends/ado/az.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,aAAa,GAAG,MAAM,CAAC;AAE7B,MAAM,UAAU,EAAE,CAAI,IAAc,EAAE,GAAW;IAC/C,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE;QACzD,GAAG;QACH,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;QAC/B,OAAO,EAAE,aAAa;KACvB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAM,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,IAAc,EAAE,GAAW;IAChD,OAAO,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE;QAC9B,GAAG;QACH,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;QAC/B,OAAO,EAAE,aAAa;KACvB,CAAC,CAAC;AACL,CAAC;AAWD,MAAM,UAAU,QAAQ,CAAI,OAAwB,EAAE,GAAW;IAC/D,MAAM,IAAI,GAAG;QACX,QAAQ;QACR,QAAQ;QACR,QAAQ;QACR,OAAO,CAAC,IAAI;QACZ,YAAY;QACZ,OAAO,CAAC,QAAQ;KACjB,CAAC;IAEF,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,OAA2B,CAAC;IAChC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,UAAU,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC9D,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE;YACzD,GAAG;YACH,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YAC/B,OAAO,EAAE,aAAa;SACvB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAM,CAAC;IACjC,CAAC;YAAS,CAAC;QACT,IAAI,OAAO,EAAE,CAAC;YACZ,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BaseBackend } from '../types.js';
|
|
2
|
+
import type { BackendCapabilities } from '../types.js';
|
|
3
|
+
import type { WorkItem, NewWorkItem, NewComment, Comment } from '../../types.js';
|
|
4
|
+
export declare class AzureDevOpsBackend extends BaseBackend {
|
|
5
|
+
private cwd;
|
|
6
|
+
private org;
|
|
7
|
+
private project;
|
|
8
|
+
private types;
|
|
9
|
+
constructor(cwd: string);
|
|
10
|
+
getCapabilities(): BackendCapabilities;
|
|
11
|
+
getStatuses(): string[];
|
|
12
|
+
getWorkItemTypes(): string[];
|
|
13
|
+
getIterations(): string[];
|
|
14
|
+
getCurrentIteration(): string;
|
|
15
|
+
setCurrentIteration(_name: string): void;
|
|
16
|
+
private escapeWiql;
|
|
17
|
+
private batchFetchWorkItems;
|
|
18
|
+
listWorkItems(iteration?: string): WorkItem[];
|
|
19
|
+
getWorkItem(id: string): WorkItem;
|
|
20
|
+
createWorkItem(data: NewWorkItem): WorkItem;
|
|
21
|
+
updateWorkItem(id: string, data: Partial<WorkItem>): WorkItem;
|
|
22
|
+
deleteWorkItem(id: string): void;
|
|
23
|
+
addComment(workItemId: string, comment: NewComment): Comment;
|
|
24
|
+
getChildren(id: string): WorkItem[];
|
|
25
|
+
getDependents(id: string): WorkItem[];
|
|
26
|
+
getItemUrl(id: string): string;
|
|
27
|
+
openItem(id: string): void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { BaseBackend } from '../types.js';
|
|
3
|
+
import { az, azExec, azInvoke } from './az.js';
|
|
4
|
+
import { parseAdoRemote } from './remote.js';
|
|
5
|
+
import { mapWorkItemToWorkItem, mapCommentToComment, mapPriorityToAdo, formatTags, extractParent, extractPredecessors, } from './mappers.js';
|
|
6
|
+
const BATCH_FIELDS = [
|
|
7
|
+
'System.Id',
|
|
8
|
+
'System.Title',
|
|
9
|
+
'System.WorkItemType',
|
|
10
|
+
'System.State',
|
|
11
|
+
'System.IterationPath',
|
|
12
|
+
'Microsoft.VSTS.Common.Priority',
|
|
13
|
+
'System.AssignedTo',
|
|
14
|
+
'System.Tags',
|
|
15
|
+
'System.Description',
|
|
16
|
+
'System.CreatedDate',
|
|
17
|
+
'System.ChangedDate',
|
|
18
|
+
];
|
|
19
|
+
export class AzureDevOpsBackend extends BaseBackend {
|
|
20
|
+
cwd;
|
|
21
|
+
org;
|
|
22
|
+
project;
|
|
23
|
+
types;
|
|
24
|
+
constructor(cwd) {
|
|
25
|
+
super();
|
|
26
|
+
this.cwd = cwd;
|
|
27
|
+
azExec(['account', 'show'], cwd);
|
|
28
|
+
const remote = parseAdoRemote(cwd);
|
|
29
|
+
this.org = remote.org;
|
|
30
|
+
this.project = remote.project;
|
|
31
|
+
this.types = azInvoke({
|
|
32
|
+
area: 'wit',
|
|
33
|
+
resource: 'workitemtypes',
|
|
34
|
+
routeParameters: `project=${this.project}`,
|
|
35
|
+
apiVersion: '7.1',
|
|
36
|
+
}, cwd).value;
|
|
37
|
+
}
|
|
38
|
+
getCapabilities() {
|
|
39
|
+
return {
|
|
40
|
+
relationships: true,
|
|
41
|
+
customTypes: false,
|
|
42
|
+
customStatuses: false,
|
|
43
|
+
iterations: true,
|
|
44
|
+
comments: true,
|
|
45
|
+
fields: {
|
|
46
|
+
priority: true,
|
|
47
|
+
assignee: true,
|
|
48
|
+
labels: true,
|
|
49
|
+
parent: true,
|
|
50
|
+
dependsOn: true,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
getStatuses() {
|
|
55
|
+
const allStates = new Set();
|
|
56
|
+
for (const type of this.types) {
|
|
57
|
+
for (const state of type.states) {
|
|
58
|
+
allStates.add(state.name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return [...allStates];
|
|
62
|
+
}
|
|
63
|
+
getWorkItemTypes() {
|
|
64
|
+
return this.types.map((t) => t.name);
|
|
65
|
+
}
|
|
66
|
+
getIterations() {
|
|
67
|
+
const iterations = az([
|
|
68
|
+
'boards',
|
|
69
|
+
'iteration',
|
|
70
|
+
'team',
|
|
71
|
+
'list',
|
|
72
|
+
'--team',
|
|
73
|
+
`${this.project} Team`,
|
|
74
|
+
'--org',
|
|
75
|
+
`https://dev.azure.com/${this.org}`,
|
|
76
|
+
'--project',
|
|
77
|
+
this.project,
|
|
78
|
+
], this.cwd);
|
|
79
|
+
return iterations.map((i) => i.path);
|
|
80
|
+
}
|
|
81
|
+
getCurrentIteration() {
|
|
82
|
+
const iterations = az([
|
|
83
|
+
'boards',
|
|
84
|
+
'iteration',
|
|
85
|
+
'team',
|
|
86
|
+
'list',
|
|
87
|
+
'--team',
|
|
88
|
+
`${this.project} Team`,
|
|
89
|
+
'--timeframe',
|
|
90
|
+
'current',
|
|
91
|
+
'--org',
|
|
92
|
+
`https://dev.azure.com/${this.org}`,
|
|
93
|
+
'--project',
|
|
94
|
+
this.project,
|
|
95
|
+
], this.cwd);
|
|
96
|
+
return iterations[0]?.path ?? '';
|
|
97
|
+
}
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
99
|
+
setCurrentIteration(_name) {
|
|
100
|
+
// No-op — current iteration is determined by date range in ADO
|
|
101
|
+
}
|
|
102
|
+
escapeWiql(value) {
|
|
103
|
+
return value.replace(/'/g, "''");
|
|
104
|
+
}
|
|
105
|
+
batchFetchWorkItems(ids) {
|
|
106
|
+
const CHUNK_SIZE = 200;
|
|
107
|
+
const items = [];
|
|
108
|
+
for (let i = 0; i < ids.length; i += CHUNK_SIZE) {
|
|
109
|
+
const chunk = ids.slice(i, i + CHUNK_SIZE);
|
|
110
|
+
const batchResult = azInvoke({
|
|
111
|
+
area: 'wit',
|
|
112
|
+
resource: 'workitemsbatch',
|
|
113
|
+
httpMethod: 'POST',
|
|
114
|
+
body: { ids: chunk, fields: BATCH_FIELDS, $expand: 4 },
|
|
115
|
+
apiVersion: '7.1',
|
|
116
|
+
}, this.cwd);
|
|
117
|
+
items.push(...batchResult.value.map(mapWorkItemToWorkItem));
|
|
118
|
+
}
|
|
119
|
+
return items;
|
|
120
|
+
}
|
|
121
|
+
listWorkItems(iteration) {
|
|
122
|
+
let wiql = `SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = '${this.escapeWiql(this.project)}'`;
|
|
123
|
+
if (iteration) {
|
|
124
|
+
wiql += ` AND [System.IterationPath] = '${this.escapeWiql(iteration)}'`;
|
|
125
|
+
}
|
|
126
|
+
const queryResult = az([
|
|
127
|
+
'boards',
|
|
128
|
+
'query',
|
|
129
|
+
'--wiql',
|
|
130
|
+
wiql,
|
|
131
|
+
'--org',
|
|
132
|
+
`https://dev.azure.com/${this.org}`,
|
|
133
|
+
'--project',
|
|
134
|
+
this.project,
|
|
135
|
+
], this.cwd);
|
|
136
|
+
const ids = queryResult.workItems.map((w) => w.id);
|
|
137
|
+
if (ids.length === 0)
|
|
138
|
+
return [];
|
|
139
|
+
const items = this.batchFetchWorkItems(ids);
|
|
140
|
+
items.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
141
|
+
return items;
|
|
142
|
+
}
|
|
143
|
+
getWorkItem(id) {
|
|
144
|
+
const ado = az([
|
|
145
|
+
'boards',
|
|
146
|
+
'work-item',
|
|
147
|
+
'show',
|
|
148
|
+
'--id',
|
|
149
|
+
id,
|
|
150
|
+
'--expand',
|
|
151
|
+
'relations',
|
|
152
|
+
'--org',
|
|
153
|
+
`https://dev.azure.com/${this.org}`,
|
|
154
|
+
'--project',
|
|
155
|
+
this.project,
|
|
156
|
+
], this.cwd);
|
|
157
|
+
const item = mapWorkItemToWorkItem(ado);
|
|
158
|
+
// Fetch comments
|
|
159
|
+
const commentResult = azInvoke({
|
|
160
|
+
area: 'wit',
|
|
161
|
+
resource: 'comments',
|
|
162
|
+
routeParameters: `workItemId=${id}`,
|
|
163
|
+
apiVersion: '7.1',
|
|
164
|
+
}, this.cwd);
|
|
165
|
+
item.comments = (commentResult.comments ?? []).map(mapCommentToComment);
|
|
166
|
+
return item;
|
|
167
|
+
}
|
|
168
|
+
createWorkItem(data) {
|
|
169
|
+
this.validateFields(data);
|
|
170
|
+
const args = [
|
|
171
|
+
'boards',
|
|
172
|
+
'work-item',
|
|
173
|
+
'create',
|
|
174
|
+
'--type',
|
|
175
|
+
data.type,
|
|
176
|
+
'--title',
|
|
177
|
+
data.title,
|
|
178
|
+
'--org',
|
|
179
|
+
`https://dev.azure.com/${this.org}`,
|
|
180
|
+
'--project',
|
|
181
|
+
this.project,
|
|
182
|
+
];
|
|
183
|
+
const fields = [];
|
|
184
|
+
if (data.status)
|
|
185
|
+
fields.push(`System.State=${data.status}`);
|
|
186
|
+
if (data.iteration)
|
|
187
|
+
fields.push(`System.IterationPath=${data.iteration}`);
|
|
188
|
+
if (data.priority)
|
|
189
|
+
fields.push(`Microsoft.VSTS.Common.Priority=${mapPriorityToAdo(data.priority)}`);
|
|
190
|
+
if (data.assignee)
|
|
191
|
+
fields.push(`System.AssignedTo=${data.assignee}`);
|
|
192
|
+
if (data.labels.length > 0)
|
|
193
|
+
fields.push(`System.Tags=${formatTags(data.labels)}`);
|
|
194
|
+
if (data.description)
|
|
195
|
+
fields.push(`System.Description=${data.description}`);
|
|
196
|
+
for (const field of fields) {
|
|
197
|
+
args.push('--fields', field);
|
|
198
|
+
}
|
|
199
|
+
const created = az(args, this.cwd);
|
|
200
|
+
const createdId = String(created.id);
|
|
201
|
+
// Add parent relation if specified
|
|
202
|
+
if (data.parent) {
|
|
203
|
+
azExec([
|
|
204
|
+
'boards',
|
|
205
|
+
'work-item',
|
|
206
|
+
'relation',
|
|
207
|
+
'add',
|
|
208
|
+
'--id',
|
|
209
|
+
createdId,
|
|
210
|
+
'--relation-type',
|
|
211
|
+
'System.LinkTypes.Hierarchy-Reverse',
|
|
212
|
+
'--target-id',
|
|
213
|
+
data.parent,
|
|
214
|
+
'--org',
|
|
215
|
+
`https://dev.azure.com/${this.org}`,
|
|
216
|
+
], this.cwd);
|
|
217
|
+
}
|
|
218
|
+
// Add dependency relations
|
|
219
|
+
for (const depId of data.dependsOn) {
|
|
220
|
+
azExec([
|
|
221
|
+
'boards',
|
|
222
|
+
'work-item',
|
|
223
|
+
'relation',
|
|
224
|
+
'add',
|
|
225
|
+
'--id',
|
|
226
|
+
createdId,
|
|
227
|
+
'--relation-type',
|
|
228
|
+
'System.LinkTypes.Dependency-Reverse',
|
|
229
|
+
'--target-id',
|
|
230
|
+
depId,
|
|
231
|
+
'--org',
|
|
232
|
+
`https://dev.azure.com/${this.org}`,
|
|
233
|
+
], this.cwd);
|
|
234
|
+
}
|
|
235
|
+
return this.getWorkItem(createdId);
|
|
236
|
+
}
|
|
237
|
+
updateWorkItem(id, data) {
|
|
238
|
+
this.validateFields(data);
|
|
239
|
+
const args = [
|
|
240
|
+
'boards',
|
|
241
|
+
'work-item',
|
|
242
|
+
'update',
|
|
243
|
+
'--id',
|
|
244
|
+
id,
|
|
245
|
+
'--org',
|
|
246
|
+
`https://dev.azure.com/${this.org}`,
|
|
247
|
+
'--project',
|
|
248
|
+
this.project,
|
|
249
|
+
];
|
|
250
|
+
const fields = [];
|
|
251
|
+
if (data.title !== undefined)
|
|
252
|
+
fields.push(`System.Title=${data.title}`);
|
|
253
|
+
if (data.status !== undefined)
|
|
254
|
+
fields.push(`System.State=${data.status}`);
|
|
255
|
+
if (data.iteration !== undefined)
|
|
256
|
+
fields.push(`System.IterationPath=${data.iteration}`);
|
|
257
|
+
if (data.priority !== undefined)
|
|
258
|
+
fields.push(`Microsoft.VSTS.Common.Priority=${mapPriorityToAdo(data.priority)}`);
|
|
259
|
+
if (data.assignee !== undefined)
|
|
260
|
+
fields.push(`System.AssignedTo=${data.assignee}`);
|
|
261
|
+
if (data.labels !== undefined)
|
|
262
|
+
fields.push(`System.Tags=${formatTags(data.labels)}`);
|
|
263
|
+
if (data.description !== undefined)
|
|
264
|
+
fields.push(`System.Description=${data.description}`);
|
|
265
|
+
for (const field of fields) {
|
|
266
|
+
args.push('--fields', field);
|
|
267
|
+
}
|
|
268
|
+
if (fields.length > 0) {
|
|
269
|
+
az(args, this.cwd);
|
|
270
|
+
}
|
|
271
|
+
// Handle parent relation changes
|
|
272
|
+
if (data.parent !== undefined) {
|
|
273
|
+
const current = az([
|
|
274
|
+
'boards',
|
|
275
|
+
'work-item',
|
|
276
|
+
'show',
|
|
277
|
+
'--id',
|
|
278
|
+
id,
|
|
279
|
+
'--expand',
|
|
280
|
+
'relations',
|
|
281
|
+
'--org',
|
|
282
|
+
`https://dev.azure.com/${this.org}`,
|
|
283
|
+
'--project',
|
|
284
|
+
this.project,
|
|
285
|
+
], this.cwd);
|
|
286
|
+
const currentParent = extractParent(current.relations);
|
|
287
|
+
if (currentParent && currentParent !== data.parent) {
|
|
288
|
+
azExec([
|
|
289
|
+
'boards',
|
|
290
|
+
'work-item',
|
|
291
|
+
'relation',
|
|
292
|
+
'remove',
|
|
293
|
+
'--id',
|
|
294
|
+
id,
|
|
295
|
+
'--relation-type',
|
|
296
|
+
'System.LinkTypes.Hierarchy-Reverse',
|
|
297
|
+
'--target-id',
|
|
298
|
+
currentParent,
|
|
299
|
+
'--org',
|
|
300
|
+
`https://dev.azure.com/${this.org}`,
|
|
301
|
+
'--yes',
|
|
302
|
+
], this.cwd);
|
|
303
|
+
}
|
|
304
|
+
if (data.parent && data.parent !== currentParent) {
|
|
305
|
+
azExec([
|
|
306
|
+
'boards',
|
|
307
|
+
'work-item',
|
|
308
|
+
'relation',
|
|
309
|
+
'add',
|
|
310
|
+
'--id',
|
|
311
|
+
id,
|
|
312
|
+
'--relation-type',
|
|
313
|
+
'System.LinkTypes.Hierarchy-Reverse',
|
|
314
|
+
'--target-id',
|
|
315
|
+
data.parent,
|
|
316
|
+
'--org',
|
|
317
|
+
`https://dev.azure.com/${this.org}`,
|
|
318
|
+
], this.cwd);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// Handle dependency relation changes
|
|
322
|
+
if (data.dependsOn !== undefined) {
|
|
323
|
+
const current = az([
|
|
324
|
+
'boards',
|
|
325
|
+
'work-item',
|
|
326
|
+
'show',
|
|
327
|
+
'--id',
|
|
328
|
+
id,
|
|
329
|
+
'--expand',
|
|
330
|
+
'relations',
|
|
331
|
+
'--org',
|
|
332
|
+
`https://dev.azure.com/${this.org}`,
|
|
333
|
+
'--project',
|
|
334
|
+
this.project,
|
|
335
|
+
], this.cwd);
|
|
336
|
+
const currentDeps = new Set(extractPredecessors(current.relations));
|
|
337
|
+
const newDeps = new Set(data.dependsOn);
|
|
338
|
+
// Remove deps that are no longer in the list
|
|
339
|
+
for (const dep of currentDeps) {
|
|
340
|
+
if (!newDeps.has(dep)) {
|
|
341
|
+
azExec([
|
|
342
|
+
'boards',
|
|
343
|
+
'work-item',
|
|
344
|
+
'relation',
|
|
345
|
+
'remove',
|
|
346
|
+
'--id',
|
|
347
|
+
id,
|
|
348
|
+
'--relation-type',
|
|
349
|
+
'System.LinkTypes.Dependency-Reverse',
|
|
350
|
+
'--target-id',
|
|
351
|
+
dep,
|
|
352
|
+
'--org',
|
|
353
|
+
`https://dev.azure.com/${this.org}`,
|
|
354
|
+
'--yes',
|
|
355
|
+
], this.cwd);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Add deps that are new
|
|
359
|
+
for (const dep of newDeps) {
|
|
360
|
+
if (!currentDeps.has(dep)) {
|
|
361
|
+
azExec([
|
|
362
|
+
'boards',
|
|
363
|
+
'work-item',
|
|
364
|
+
'relation',
|
|
365
|
+
'add',
|
|
366
|
+
'--id',
|
|
367
|
+
id,
|
|
368
|
+
'--relation-type',
|
|
369
|
+
'System.LinkTypes.Dependency-Reverse',
|
|
370
|
+
'--target-id',
|
|
371
|
+
dep,
|
|
372
|
+
'--org',
|
|
373
|
+
`https://dev.azure.com/${this.org}`,
|
|
374
|
+
], this.cwd);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return this.getWorkItem(id);
|
|
379
|
+
}
|
|
380
|
+
deleteWorkItem(id) {
|
|
381
|
+
azExec([
|
|
382
|
+
'boards',
|
|
383
|
+
'work-item',
|
|
384
|
+
'delete',
|
|
385
|
+
'--id',
|
|
386
|
+
id,
|
|
387
|
+
'--yes',
|
|
388
|
+
'--org',
|
|
389
|
+
`https://dev.azure.com/${this.org}`,
|
|
390
|
+
'--project',
|
|
391
|
+
this.project,
|
|
392
|
+
], this.cwd);
|
|
393
|
+
}
|
|
394
|
+
addComment(workItemId, comment) {
|
|
395
|
+
azInvoke({
|
|
396
|
+
area: 'wit',
|
|
397
|
+
resource: 'comments',
|
|
398
|
+
routeParameters: `workItemId=${workItemId}`,
|
|
399
|
+
httpMethod: 'POST',
|
|
400
|
+
body: { text: comment.body },
|
|
401
|
+
apiVersion: '7.1',
|
|
402
|
+
}, this.cwd);
|
|
403
|
+
return {
|
|
404
|
+
author: comment.author,
|
|
405
|
+
date: new Date().toISOString(),
|
|
406
|
+
body: comment.body,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
getChildren(id) {
|
|
410
|
+
const numericId = parseInt(id, 10);
|
|
411
|
+
if (isNaN(numericId))
|
|
412
|
+
throw new Error(`Invalid work item ID: "${id}"`);
|
|
413
|
+
const wiql = `SELECT [System.Id] FROM WorkItemLinks WHERE [Source].[System.Id] = ${numericId} AND [System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward' MODE (MustContain)`;
|
|
414
|
+
const queryResult = az([
|
|
415
|
+
'boards',
|
|
416
|
+
'query',
|
|
417
|
+
'--wiql',
|
|
418
|
+
wiql,
|
|
419
|
+
'--org',
|
|
420
|
+
`https://dev.azure.com/${this.org}`,
|
|
421
|
+
'--project',
|
|
422
|
+
this.project,
|
|
423
|
+
], this.cwd);
|
|
424
|
+
const ids = queryResult.workItems?.map((w) => w.id) ?? [];
|
|
425
|
+
if (ids.length === 0)
|
|
426
|
+
return [];
|
|
427
|
+
return this.batchFetchWorkItems(ids);
|
|
428
|
+
}
|
|
429
|
+
getDependents(id) {
|
|
430
|
+
const numericId = parseInt(id, 10);
|
|
431
|
+
if (isNaN(numericId))
|
|
432
|
+
throw new Error(`Invalid work item ID: "${id}"`);
|
|
433
|
+
const wiql = `SELECT [System.Id] FROM WorkItemLinks WHERE [Source].[System.Id] = ${numericId} AND [System.Links.LinkType] = 'System.LinkTypes.Dependency-Forward' MODE (MustContain)`;
|
|
434
|
+
const queryResult = az([
|
|
435
|
+
'boards',
|
|
436
|
+
'query',
|
|
437
|
+
'--wiql',
|
|
438
|
+
wiql,
|
|
439
|
+
'--org',
|
|
440
|
+
`https://dev.azure.com/${this.org}`,
|
|
441
|
+
'--project',
|
|
442
|
+
this.project,
|
|
443
|
+
], this.cwd);
|
|
444
|
+
const ids = queryResult.workItems?.map((w) => w.id) ?? [];
|
|
445
|
+
if (ids.length === 0)
|
|
446
|
+
return [];
|
|
447
|
+
return this.batchFetchWorkItems(ids);
|
|
448
|
+
}
|
|
449
|
+
getItemUrl(id) {
|
|
450
|
+
return `https://dev.azure.com/${this.org}/${encodeURIComponent(this.project)}/_workitems/edit/${id}`;
|
|
451
|
+
}
|
|
452
|
+
openItem(id) {
|
|
453
|
+
const url = this.getItemUrl(id);
|
|
454
|
+
execFileSync('open', [url]);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/backends/ado/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { WorkItem, Comment } from '../../types.js';
|
|
2
|
+
export interface AdoWorkItem {
|
|
3
|
+
id: number;
|
|
4
|
+
fields: Record<string, unknown>;
|
|
5
|
+
relations?: AdoRelation[];
|
|
6
|
+
}
|
|
7
|
+
export interface AdoRelation {
|
|
8
|
+
rel: string;
|
|
9
|
+
url: string;
|
|
10
|
+
attributes: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface AdoComment {
|
|
13
|
+
createdBy: {
|
|
14
|
+
displayName: string;
|
|
15
|
+
};
|
|
16
|
+
createdDate: string;
|
|
17
|
+
text: string;
|
|
18
|
+
}
|
|
19
|
+
export interface AdoIteration {
|
|
20
|
+
name: string;
|
|
21
|
+
path: string;
|
|
22
|
+
attributes: {
|
|
23
|
+
startDate?: string;
|
|
24
|
+
finishDate?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface AdoWorkItemType {
|
|
28
|
+
name: string;
|
|
29
|
+
states: {
|
|
30
|
+
name: string;
|
|
31
|
+
}[];
|
|
32
|
+
}
|
|
33
|
+
export declare function mapPriorityToTic(priority: number | undefined): 'critical' | 'high' | 'medium' | 'low';
|
|
34
|
+
export declare function mapPriorityToAdo(priority: string): number;
|
|
35
|
+
export declare function parseTags(tags: string | undefined): string[];
|
|
36
|
+
export declare function formatTags(tags: string[]): string;
|
|
37
|
+
export declare function extractParent(relations: AdoRelation[] | undefined): string | null;
|
|
38
|
+
export declare function extractPredecessors(relations: AdoRelation[] | undefined): string[];
|
|
39
|
+
export declare function mapWorkItemToWorkItem(ado: AdoWorkItem): WorkItem;
|
|
40
|
+
export declare function mapCommentToComment(ado: AdoComment): Comment;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import TurndownService from 'turndown';
|
|
2
|
+
const turndown = new TurndownService();
|
|
3
|
+
export function mapPriorityToTic(priority) {
|
|
4
|
+
switch (priority) {
|
|
5
|
+
case 1:
|
|
6
|
+
return 'critical';
|
|
7
|
+
case 2:
|
|
8
|
+
return 'high';
|
|
9
|
+
case 3:
|
|
10
|
+
return 'medium';
|
|
11
|
+
case 4:
|
|
12
|
+
return 'low';
|
|
13
|
+
default:
|
|
14
|
+
return 'medium';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function mapPriorityToAdo(priority) {
|
|
18
|
+
switch (priority) {
|
|
19
|
+
case 'critical':
|
|
20
|
+
return 1;
|
|
21
|
+
case 'high':
|
|
22
|
+
return 2;
|
|
23
|
+
case 'medium':
|
|
24
|
+
return 3;
|
|
25
|
+
case 'low':
|
|
26
|
+
return 4;
|
|
27
|
+
default:
|
|
28
|
+
return 3;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function parseTags(tags) {
|
|
32
|
+
if (!tags)
|
|
33
|
+
return [];
|
|
34
|
+
return tags
|
|
35
|
+
.split(';')
|
|
36
|
+
.map((t) => t.trim())
|
|
37
|
+
.filter((t) => t.length > 0);
|
|
38
|
+
}
|
|
39
|
+
export function formatTags(tags) {
|
|
40
|
+
return tags.join('; ');
|
|
41
|
+
}
|
|
42
|
+
function extractIdFromUrl(url) {
|
|
43
|
+
const match = url.match(/\/workItems\/(\d+)$/);
|
|
44
|
+
return match ? match[1] : '';
|
|
45
|
+
}
|
|
46
|
+
export function extractParent(relations) {
|
|
47
|
+
if (!relations)
|
|
48
|
+
return null;
|
|
49
|
+
const parent = relations.find((r) => r.rel === 'System.LinkTypes.Hierarchy-Reverse');
|
|
50
|
+
return parent ? extractIdFromUrl(parent.url) || null : null;
|
|
51
|
+
}
|
|
52
|
+
export function extractPredecessors(relations) {
|
|
53
|
+
if (!relations)
|
|
54
|
+
return [];
|
|
55
|
+
return relations
|
|
56
|
+
.filter((r) => r.rel === 'System.LinkTypes.Dependency-Reverse')
|
|
57
|
+
.map((r) => extractIdFromUrl(r.url))
|
|
58
|
+
.filter((id) => id !== '');
|
|
59
|
+
}
|
|
60
|
+
function htmlToMarkdown(html) {
|
|
61
|
+
if (!html)
|
|
62
|
+
return '';
|
|
63
|
+
return turndown.turndown(html);
|
|
64
|
+
}
|
|
65
|
+
export function mapWorkItemToWorkItem(ado) {
|
|
66
|
+
const fields = ado.fields;
|
|
67
|
+
const assignedTo = fields['System.AssignedTo'];
|
|
68
|
+
return {
|
|
69
|
+
id: String(ado.id),
|
|
70
|
+
title: fields['System.Title'] ?? '',
|
|
71
|
+
type: fields['System.WorkItemType'] ?? '',
|
|
72
|
+
status: fields['System.State'] ?? '',
|
|
73
|
+
iteration: fields['System.IterationPath'] ?? '',
|
|
74
|
+
priority: mapPriorityToTic(fields['Microsoft.VSTS.Common.Priority']),
|
|
75
|
+
assignee: assignedTo?.displayName ?? '',
|
|
76
|
+
labels: parseTags(fields['System.Tags']),
|
|
77
|
+
description: htmlToMarkdown(fields['System.Description']),
|
|
78
|
+
created: fields['System.CreatedDate'] ?? '',
|
|
79
|
+
updated: fields['System.ChangedDate'] ?? '',
|
|
80
|
+
parent: extractParent(ado.relations),
|
|
81
|
+
dependsOn: extractPredecessors(ado.relations),
|
|
82
|
+
comments: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function mapCommentToComment(ado) {
|
|
86
|
+
return {
|
|
87
|
+
author: ado.createdBy.displayName,
|
|
88
|
+
date: ado.createdDate,
|
|
89
|
+
body: htmlToMarkdown(ado.text),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=mappers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mappers.js","sourceRoot":"","sources":["../../../src/backends/ado/mappers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
export function parseAdoRemote(cwd) {
|
|
3
|
+
const output = execSync('git remote -v', {
|
|
4
|
+
cwd,
|
|
5
|
+
encoding: 'utf-8',
|
|
6
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
7
|
+
});
|
|
8
|
+
const lines = output.split('\n');
|
|
9
|
+
for (const line of lines) {
|
|
10
|
+
// HTTPS: https://dev.azure.com/{org}/{project}/_git/{repo}
|
|
11
|
+
const httpsMatch = line.match(/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\//);
|
|
12
|
+
if (httpsMatch) {
|
|
13
|
+
return {
|
|
14
|
+
org: httpsMatch[1],
|
|
15
|
+
project: decodeURIComponent(httpsMatch[2]),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// SSH: git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
|
|
19
|
+
const sshMatch = line.match(/ssh\.dev\.azure\.com:v3\/([^/]+)\/([^/]+)\/[^/]+/);
|
|
20
|
+
if (sshMatch) {
|
|
21
|
+
return {
|
|
22
|
+
org: sshMatch[1],
|
|
23
|
+
project: decodeURIComponent(sshMatch[2]),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// Legacy: https://{org}.visualstudio.com/{project}/_git/{repo}
|
|
27
|
+
const legacyMatch = line.match(/([^/.]+)\.visualstudio\.com\/([^/]+)\/_git\//);
|
|
28
|
+
if (legacyMatch) {
|
|
29
|
+
return {
|
|
30
|
+
org: legacyMatch[1],
|
|
31
|
+
project: decodeURIComponent(legacyMatch[2]),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
throw new Error('No Azure DevOps remote found in git remotes');
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=remote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../../src/backends/ado/remote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAO9C,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,EAAE;QACvC,GAAG;QACH,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;KAChC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,2DAA2D;QAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC3E,IAAI,UAAU,EAAE,CAAC;YACf,OAAO;gBACL,GAAG,EAAE,UAAU,CAAC,CAAC,CAAE;gBACnB,OAAO,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC;aAC5C,CAAC;QACJ,CAAC;QAED,uDAAuD;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CACzB,kDAAkD,CACnD,CAAC;QACF,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO;gBACL,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAE;gBACjB,OAAO,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC;aAC1C,CAAC;QACJ,CAAC;QAED,+DAA+D;QAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAC5B,8CAA8C,CAC/C,CAAC;QACF,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO;gBACL,GAAG,EAAE,WAAW,CAAC,CAAC,CAAE;gBACpB,OAAO,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC;aAC7C,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;AACjE,CAAC"}
|
package/dist/backends/factory.js
CHANGED
|
@@ -2,6 +2,7 @@ import { execSync } from 'node:child_process';
|
|
|
2
2
|
import { LocalBackend } from './local/index.js';
|
|
3
3
|
import { GitHubBackend } from './github/index.js';
|
|
4
4
|
import { GitLabBackend } from './gitlab/index.js';
|
|
5
|
+
import { AzureDevOpsBackend } from './ado/index.js';
|
|
5
6
|
import { readConfig } from './local/config.js';
|
|
6
7
|
export const VALID_BACKENDS = ['local', 'github', 'gitlab', 'azure'];
|
|
7
8
|
export function detectBackend(root) {
|
|
@@ -15,7 +16,9 @@ export function detectBackend(root) {
|
|
|
15
16
|
return 'github';
|
|
16
17
|
if (output.includes('gitlab.com'))
|
|
17
18
|
return 'gitlab';
|
|
18
|
-
if (output.includes('dev.azure.com')
|
|
19
|
+
if (output.includes('dev.azure.com') ||
|
|
20
|
+
output.includes('ssh.dev.azure.com') ||
|
|
21
|
+
/\w+\.visualstudio\.com/.test(output))
|
|
19
22
|
return 'azure';
|
|
20
23
|
}
|
|
21
24
|
catch {
|
|
@@ -34,7 +37,7 @@ export function createBackend(root) {
|
|
|
34
37
|
case 'gitlab':
|
|
35
38
|
return new GitLabBackend(root);
|
|
36
39
|
case 'azure':
|
|
37
|
-
|
|
40
|
+
return new AzureDevOpsBackend(root);
|
|
38
41
|
default:
|
|
39
42
|
throw new Error(`Unknown backend "${backend}". Valid backends: ${VALID_BACKENDS.join(', ')}`);
|
|
40
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/backends/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAC;AAG9E,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,EAAE;YACvC,GAAG,EAAE,IAAI;YACT,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,OAAO,QAAQ,CAAC;QACnD,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,OAAO,QAAQ,CAAC;QACnD,
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/backends/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAC;AAG9E,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,EAAE;YACvC,GAAG,EAAE,IAAI;YACT,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,OAAO,QAAQ,CAAC;QACnD,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,OAAO,QAAQ,CAAC;QACnD,IACE,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC;YAChC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YACpC,wBAAwB,CAAC,IAAI,CAAC,MAAM,CAAC;YAErC,OAAO,OAAO,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;IACxC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC;IAE1C,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,OAAO;YACV,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;QAChC,KAAK,QAAQ;YACX,OAAO,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;QACjC,KAAK,QAAQ;YACX,OAAO,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;QACjC,KAAK,OAAO;YACV,OAAO,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACtC;YACE,MAAM,IAAI,KAAK,CACb,oBAAoB,OAAO,sBAAsB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC7E,CAAC;IACN,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sascha384/tic",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Terminal UI for issue tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"ink-select-input": "^6.2.0",
|
|
38
38
|
"ink-text-input": "^6.0.0",
|
|
39
39
|
"react": "^19.2.4",
|
|
40
|
+
"turndown": "^7.2.2",
|
|
40
41
|
"yaml": "^2.8.2",
|
|
41
42
|
"zod": "^4.3.6"
|
|
42
43
|
},
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"@sindresorhus/tsconfig": "^8.1.0",
|
|
46
47
|
"@types/node": "^25.1.0",
|
|
47
48
|
"@types/react": "^19.2.10",
|
|
49
|
+
"@types/turndown": "^5.0.6",
|
|
48
50
|
"eslint": "^9.39.2",
|
|
49
51
|
"eslint-config-prettier": "^10.1.8",
|
|
50
52
|
"husky": "^9.1.7",
|
|
@@ -55,7 +57,9 @@
|
|
|
55
57
|
"vitest": "^4.0.18"
|
|
56
58
|
},
|
|
57
59
|
"release": {
|
|
58
|
-
"branches": [
|
|
60
|
+
"branches": [
|
|
61
|
+
"main"
|
|
62
|
+
],
|
|
59
63
|
"plugins": [
|
|
60
64
|
"@semantic-release/commit-analyzer",
|
|
61
65
|
"@semantic-release/release-notes-generator",
|