@knpkv/jira-cli 0.1.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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/IssueService.d.ts +144 -0
- package/dist/IssueService.d.ts.map +1 -0
- package/dist/IssueService.js +250 -0
- package/dist/IssueService.js.map +1 -0
- package/dist/JiraAuth.d.ts +84 -0
- package/dist/JiraAuth.d.ts.map +1 -0
- package/dist/JiraAuth.js +246 -0
- package/dist/JiraAuth.js.map +1 -0
- package/dist/JiraCliError.d.ts +42 -0
- package/dist/JiraCliError.d.ts.map +1 -0
- package/dist/JiraCliError.js +35 -0
- package/dist/JiraCliError.js.map +1 -0
- package/dist/MarkdownWriter.d.ts +56 -0
- package/dist/MarkdownWriter.d.ts.map +1 -0
- package/dist/MarkdownWriter.js +66 -0
- package/dist/MarkdownWriter.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +39 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/auth.d.ts +22 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +89 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/errorHandler.d.ts +13 -0
- package/dist/commands/errorHandler.d.ts.map +1 -0
- package/dist/commands/errorHandler.js +13 -0
- package/dist/commands/errorHandler.js.map +1 -0
- package/dist/commands/get.d.ts +13 -0
- package/dist/commands/get.d.ts.map +1 -0
- package/dist/commands/get.js +25 -0
- package/dist/commands/get.js.map +1 -0
- package/dist/commands/index.d.ts +11 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +11 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/layers.d.ts +44 -0
- package/dist/commands/layers.d.ts.map +1 -0
- package/dist/commands/layers.js +100 -0
- package/dist/commands/layers.js.map +1 -0
- package/dist/commands/search.d.ts +18 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +64 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/NodeLayers.d.ts +7 -0
- package/dist/internal/NodeLayers.d.ts.map +1 -0
- package/dist/internal/NodeLayers.js +15 -0
- package/dist/internal/NodeLayers.js.map +1 -0
- package/dist/internal/frontmatter.d.ts +60 -0
- package/dist/internal/frontmatter.d.ts.map +1 -0
- package/dist/internal/frontmatter.js +130 -0
- package/dist/internal/frontmatter.js.map +1 -0
- package/dist/internal/jqlBuilder.d.ts +39 -0
- package/dist/internal/jqlBuilder.d.ts.map +1 -0
- package/dist/internal/jqlBuilder.js +47 -0
- package/dist/internal/jqlBuilder.js.map +1 -0
- package/dist/internal/oauthServer.d.ts +55 -0
- package/dist/internal/oauthServer.d.ts.map +1 -0
- package/dist/internal/oauthServer.js +113 -0
- package/dist/internal/oauthServer.js.map +1 -0
- package/package.json +86 -0
- package/src/IssueService.ts +378 -0
- package/src/JiraAuth.ts +476 -0
- package/src/JiraCliError.ts +44 -0
- package/src/MarkdownWriter.ts +112 -0
- package/src/bin.ts +62 -0
- package/src/commands/auth.ts +124 -0
- package/src/commands/errorHandler.ts +14 -0
- package/src/commands/get.ts +42 -0
- package/src/commands/index.ts +11 -0
- package/src/commands/layers.ts +142 -0
- package/src/commands/search.ts +102 -0
- package/src/index.ts +26 -0
- package/src/internal/NodeLayers.ts +17 -0
- package/src/internal/frontmatter.ts +170 -0
- package/src/internal/jqlBuilder.ts +49 -0
- package/src/internal/oauthServer.ts +203 -0
- package/test/jqlBuilder.test.ts +45 -0
- package/tsconfig.json +32 -0
- package/vitest.config.ts +12 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 knpkv
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# @knpkv/jira-cli
|
|
2
|
+
|
|
3
|
+
CLI tool to fetch Jira tickets and export to markdown.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @knpkv/jira-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or link globally for development:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd packages/jira-cli && pnpm link --global
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
### 1. Create OAuth App
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
jira auth create
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Opens Atlassian Developer Console. Create a new OAuth 2.0 (3LO) app with:
|
|
26
|
+
|
|
27
|
+
**Permissions:**
|
|
28
|
+
|
|
29
|
+
- Jira API: `read:jira-work`, `read:jira-user`
|
|
30
|
+
- User Identity API: `read:me`
|
|
31
|
+
|
|
32
|
+
**Callback URL:**
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
http://localhost:8585/callback
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Configure Credentials
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
jira auth configure --client-id <ID> --client-secret <SECRET>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. Login
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
jira auth login
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### Search by JQL
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
jira search 'project = PROJ AND status = Done'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Search by Fix Version
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
jira search --by-version "1.0.0"
|
|
62
|
+
jira search --by-version "1.0.0" --project PROJ
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Options
|
|
66
|
+
|
|
67
|
+
| Option | Alias | Description | Default |
|
|
68
|
+
| --------------- | ----- | ---------------------------------------- | ---------------- |
|
|
69
|
+
| `--by-version` | `-v` | Search by fix version | - |
|
|
70
|
+
| `--project` | `-p` | Filter by project key | - |
|
|
71
|
+
| `--output-dir` | `-o` | Output directory | `./jira-tickets` |
|
|
72
|
+
| `--format` | `-f` | `multi` (one file per issue) or `single` | `multi` |
|
|
73
|
+
| `--max-results` | `-m` | Max results to fetch | `100` |
|
|
74
|
+
|
|
75
|
+
### Output Formats
|
|
76
|
+
|
|
77
|
+
**Multi (default):** One markdown file per issue with YAML front-matter.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
./jira-tickets/
|
|
81
|
+
├── PROJ-123.md
|
|
82
|
+
├── PROJ-124.md
|
|
83
|
+
└── PROJ-125.md
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Single:** All issues in one combined file.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
./jira-tickets/
|
|
90
|
+
└── jira-export.md
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Auth Commands
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
jira auth create # Open Atlassian console to create OAuth app
|
|
97
|
+
jira auth configure # Set client ID and secret
|
|
98
|
+
jira auth login # Authenticate via OAuth
|
|
99
|
+
jira auth logout # Remove stored credentials
|
|
100
|
+
jira auth status # Show current auth status
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Programmatic Usage
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { Effect, Layer } from "effect"
|
|
107
|
+
import * as Redacted from "effect/Redacted"
|
|
108
|
+
import { IssueService, IssueServiceLayer } from "@knpkv/jira-cli"
|
|
109
|
+
import { JiraApiClient, JiraApiConfig, toEffect } from "@knpkv/jira-api-client"
|
|
110
|
+
|
|
111
|
+
const configLayer = Layer.succeed(JiraApiConfig, {
|
|
112
|
+
baseUrl: "https://mysite.atlassian.net",
|
|
113
|
+
auth: {
|
|
114
|
+
type: "basic",
|
|
115
|
+
email: "user@example.com",
|
|
116
|
+
apiToken: Redacted.make("your-api-token")
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Using IssueService (high-level)
|
|
121
|
+
const program = Effect.gen(function* () {
|
|
122
|
+
const service = yield* IssueService
|
|
123
|
+
const issues = yield* service.searchAll('fixVersion = "1.0.0"')
|
|
124
|
+
console.log(`Found ${issues.length} issues`)
|
|
125
|
+
}).pipe(Effect.provide(IssueServiceLayer), Effect.provide(JiraApiClient.layer), Effect.provide(configLayer))
|
|
126
|
+
|
|
127
|
+
// Or using JiraApiClient directly (low-level)
|
|
128
|
+
const direct = Effect.gen(function* () {
|
|
129
|
+
const client = yield* JiraApiClient
|
|
130
|
+
const issue = yield* toEffect(
|
|
131
|
+
client.v3.client.GET("/rest/api/3/issue/{issueIdOrKey}", {
|
|
132
|
+
params: { path: { issueIdOrKey: "PROJ-123" } }
|
|
133
|
+
})
|
|
134
|
+
)
|
|
135
|
+
console.log(issue.fields?.summary)
|
|
136
|
+
}).pipe(Effect.provide(JiraApiClient.layer), Effect.provide(configLayer))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jira issue fetching with field extraction and cursor-based pagination.
|
|
3
|
+
*
|
|
4
|
+
* **Mental model**
|
|
5
|
+
*
|
|
6
|
+
* - **API → domain mapping**: {@link IssueService} wraps the generated V3 client, extracting
|
|
7
|
+
* typed {@link Issue} objects from the loosely-typed API response via helper functions
|
|
8
|
+
* like `extractDisplayName` and `extractNameArray`.
|
|
9
|
+
* - **Rendered fields**: Requests include `expand: "renderedFields"` to get HTML-rendered
|
|
10
|
+
* descriptions and comments, falling back to plain text.
|
|
11
|
+
* - **Pagination guard**: {@link IssueServiceShape.searchAll} iterates pages using
|
|
12
|
+
* `nextPageToken` with a MAX_PAGES (1000) safety limit.
|
|
13
|
+
*
|
|
14
|
+
* **Common tasks**
|
|
15
|
+
*
|
|
16
|
+
* - Fetch single issue: `service.getByKey("PROJ-123")`
|
|
17
|
+
* - Search with pagination: `service.searchAll(jql)`
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import { JiraApiClient } from "@knpkv/jira-api-client";
|
|
22
|
+
import * as Context from "effect/Context";
|
|
23
|
+
import * as Effect from "effect/Effect";
|
|
24
|
+
import * as Layer from "effect/Layer";
|
|
25
|
+
import { JiraApiError } from "./JiraCliError.js";
|
|
26
|
+
declare const SiteUrl_base: Context.TagClass<SiteUrl, "@knpkv/jira-cli/SiteUrl", string>;
|
|
27
|
+
/**
|
|
28
|
+
* Site URL configuration for issue links.
|
|
29
|
+
*
|
|
30
|
+
* @category Config
|
|
31
|
+
*/
|
|
32
|
+
export declare class SiteUrl extends SiteUrl_base {
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Attachment metadata.
|
|
36
|
+
*
|
|
37
|
+
* @category Types
|
|
38
|
+
*/
|
|
39
|
+
export interface Attachment {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly filename: string;
|
|
42
|
+
readonly url: string;
|
|
43
|
+
readonly mimeType: string;
|
|
44
|
+
readonly size: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Comment on an issue.
|
|
48
|
+
*
|
|
49
|
+
* @category Types
|
|
50
|
+
*/
|
|
51
|
+
export interface Comment {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly author: string;
|
|
54
|
+
readonly body: string;
|
|
55
|
+
readonly created: Date;
|
|
56
|
+
readonly updated: Date;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Jira issue with relevant fields.
|
|
60
|
+
*
|
|
61
|
+
* @category Types
|
|
62
|
+
*/
|
|
63
|
+
export interface Issue {
|
|
64
|
+
readonly key: string;
|
|
65
|
+
readonly id: string;
|
|
66
|
+
readonly summary: string;
|
|
67
|
+
readonly status: string;
|
|
68
|
+
readonly type: string;
|
|
69
|
+
readonly priority: string | null;
|
|
70
|
+
readonly assignee: string | null;
|
|
71
|
+
readonly reporter: string | null;
|
|
72
|
+
readonly created: Date;
|
|
73
|
+
readonly updated: Date;
|
|
74
|
+
readonly fixVersions: ReadonlyArray<string>;
|
|
75
|
+
readonly labels: ReadonlyArray<string>;
|
|
76
|
+
readonly components: ReadonlyArray<string>;
|
|
77
|
+
readonly description: string;
|
|
78
|
+
readonly attachments: ReadonlyArray<Attachment>;
|
|
79
|
+
readonly comments: ReadonlyArray<Comment>;
|
|
80
|
+
readonly url: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Search options for issue queries.
|
|
84
|
+
*
|
|
85
|
+
* @category Types
|
|
86
|
+
*/
|
|
87
|
+
export interface SearchOptions {
|
|
88
|
+
readonly maxResults?: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Search result with pagination info.
|
|
92
|
+
*
|
|
93
|
+
* @category Types
|
|
94
|
+
*/
|
|
95
|
+
export interface SearchResult {
|
|
96
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
97
|
+
/** True if this is the last page (API uses cursor pagination, no total count) */
|
|
98
|
+
readonly isLast: boolean;
|
|
99
|
+
readonly nextPageToken: string | null;
|
|
100
|
+
readonly maxResults: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* IssueService interface.
|
|
104
|
+
*
|
|
105
|
+
* @category Services
|
|
106
|
+
*/
|
|
107
|
+
export interface IssueServiceShape {
|
|
108
|
+
/** Get a single issue by key */
|
|
109
|
+
readonly getByKey: (key: string) => Effect.Effect<Issue, JiraApiError>;
|
|
110
|
+
/** Search issues by JQL query */
|
|
111
|
+
readonly search: (jql: string, options?: SearchOptions) => Effect.Effect<SearchResult, JiraApiError>;
|
|
112
|
+
/** Search all issues by JQL query (handles pagination) */
|
|
113
|
+
readonly searchAll: (jql: string, options?: {
|
|
114
|
+
readonly maxResults?: number;
|
|
115
|
+
}) => Effect.Effect<ReadonlyArray<Issue>, JiraApiError>;
|
|
116
|
+
}
|
|
117
|
+
declare const IssueService_base: Context.TagClass<IssueService, "@knpkv/jira-cli/IssueService", IssueServiceShape>;
|
|
118
|
+
/**
|
|
119
|
+
* IssueService tag.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* import { Effect } from "effect"
|
|
124
|
+
* import { IssueService } from "@knpkv/jira-cli/IssueService"
|
|
125
|
+
*
|
|
126
|
+
* Effect.gen(function* () {
|
|
127
|
+
* const service = yield* IssueService
|
|
128
|
+
* const issues = yield* service.searchAll('fixVersion = "1.0.0"')
|
|
129
|
+
* console.log(`Found ${issues.length} issues`)
|
|
130
|
+
* })
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* @category Services
|
|
134
|
+
*/
|
|
135
|
+
export declare class IssueService extends IssueService_base {
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Layer for IssueService.
|
|
139
|
+
*
|
|
140
|
+
* @category Layers
|
|
141
|
+
*/
|
|
142
|
+
export declare const layer: Layer.Layer<IssueService, never, SiteUrl | JiraApiClient>;
|
|
143
|
+
export {};
|
|
144
|
+
//# sourceMappingURL=IssueService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IssueService.d.ts","sourceRoot":"","sources":["../src/IssueService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jira issue fetching with field extraction and cursor-based pagination.
|
|
3
|
+
*
|
|
4
|
+
* **Mental model**
|
|
5
|
+
*
|
|
6
|
+
* - **API → domain mapping**: {@link IssueService} wraps the generated V3 client, extracting
|
|
7
|
+
* typed {@link Issue} objects from the loosely-typed API response via helper functions
|
|
8
|
+
* like `extractDisplayName` and `extractNameArray`.
|
|
9
|
+
* - **Rendered fields**: Requests include `expand: "renderedFields"` to get HTML-rendered
|
|
10
|
+
* descriptions and comments, falling back to plain text.
|
|
11
|
+
* - **Pagination guard**: {@link IssueServiceShape.searchAll} iterates pages using
|
|
12
|
+
* `nextPageToken` with a MAX_PAGES (1000) safety limit.
|
|
13
|
+
*
|
|
14
|
+
* **Common tasks**
|
|
15
|
+
*
|
|
16
|
+
* - Fetch single issue: `service.getByKey("PROJ-123")`
|
|
17
|
+
* - Search with pagination: `service.searchAll(jql)`
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import { JiraApiClient, toEffect } from "@knpkv/jira-api-client";
|
|
22
|
+
import * as Context from "effect/Context";
|
|
23
|
+
import * as Effect from "effect/Effect";
|
|
24
|
+
import * as Layer from "effect/Layer";
|
|
25
|
+
import { JiraApiError } from "./JiraCliError.js";
|
|
26
|
+
/**
|
|
27
|
+
* Site URL configuration for issue links.
|
|
28
|
+
*
|
|
29
|
+
* @category Config
|
|
30
|
+
*/
|
|
31
|
+
export class SiteUrl extends Context.Tag("@knpkv/jira-cli/SiteUrl")() {
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* IssueService tag.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { Effect } from "effect"
|
|
39
|
+
* import { IssueService } from "@knpkv/jira-cli/IssueService"
|
|
40
|
+
*
|
|
41
|
+
* Effect.gen(function* () {
|
|
42
|
+
* const service = yield* IssueService
|
|
43
|
+
* const issues = yield* service.searchAll('fixVersion = "1.0.0"')
|
|
44
|
+
* console.log(`Found ${issues.length} issues`)
|
|
45
|
+
* })
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @category Services
|
|
49
|
+
*/
|
|
50
|
+
export class IssueService extends Context.Tag("@knpkv/jira-cli/IssueService")() {
|
|
51
|
+
}
|
|
52
|
+
const FIELDS = [
|
|
53
|
+
"summary",
|
|
54
|
+
"description",
|
|
55
|
+
"status",
|
|
56
|
+
"issuetype",
|
|
57
|
+
"priority",
|
|
58
|
+
"assignee",
|
|
59
|
+
"reporter",
|
|
60
|
+
"created",
|
|
61
|
+
"updated",
|
|
62
|
+
"fixVersions",
|
|
63
|
+
"labels",
|
|
64
|
+
"components",
|
|
65
|
+
"attachment",
|
|
66
|
+
"comment"
|
|
67
|
+
];
|
|
68
|
+
/**
|
|
69
|
+
* Extract string from a field that may be an object with displayName/name.
|
|
70
|
+
*/
|
|
71
|
+
const extractDisplayName = (field) => {
|
|
72
|
+
if (field === null || field === undefined)
|
|
73
|
+
return null;
|
|
74
|
+
if (typeof field === "string")
|
|
75
|
+
return field;
|
|
76
|
+
if (typeof field === "object") {
|
|
77
|
+
const obj = field;
|
|
78
|
+
if (typeof obj["displayName"] === "string")
|
|
79
|
+
return obj["displayName"];
|
|
80
|
+
if (typeof obj["name"] === "string")
|
|
81
|
+
return obj["name"];
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Extract array of strings from a field that may be array of objects with name.
|
|
87
|
+
*/
|
|
88
|
+
const extractNameArray = (field) => {
|
|
89
|
+
if (!Array.isArray(field))
|
|
90
|
+
return [];
|
|
91
|
+
return field
|
|
92
|
+
.map((item) => {
|
|
93
|
+
if (typeof item === "string")
|
|
94
|
+
return item;
|
|
95
|
+
if (typeof item === "object" && item !== null) {
|
|
96
|
+
const obj = item;
|
|
97
|
+
if (typeof obj["name"] === "string")
|
|
98
|
+
return obj["name"];
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
})
|
|
102
|
+
.filter((x) => x !== null);
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Parse date from unknown value, returning epoch date if invalid.
|
|
106
|
+
*/
|
|
107
|
+
const parseDate = (val) => {
|
|
108
|
+
const str = String(val ?? "");
|
|
109
|
+
if (!str)
|
|
110
|
+
return new Date(0);
|
|
111
|
+
const date = new Date(str);
|
|
112
|
+
return isNaN(date.getTime()) ? new Date(0) : date;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Map IssueBean from API to our Issue type.
|
|
116
|
+
*/
|
|
117
|
+
const mapIssue = (bean, baseUrl) => {
|
|
118
|
+
const fields = (bean["fields"] ?? {});
|
|
119
|
+
const renderedFields = (bean["renderedFields"] ?? {});
|
|
120
|
+
const key = String(bean["key"] ?? "");
|
|
121
|
+
const id = String(bean["id"] ?? "");
|
|
122
|
+
// Extract attachments
|
|
123
|
+
const attachmentField = fields["attachment"];
|
|
124
|
+
const attachments = Array.isArray(attachmentField)
|
|
125
|
+
? attachmentField.map((a) => {
|
|
126
|
+
const att = a;
|
|
127
|
+
return {
|
|
128
|
+
id: String(att["id"] ?? ""),
|
|
129
|
+
filename: String(att["filename"] ?? ""),
|
|
130
|
+
url: String(att["content"] ?? ""),
|
|
131
|
+
mimeType: String(att["mimeType"] ?? ""),
|
|
132
|
+
size: Number(att["size"] ?? 0)
|
|
133
|
+
};
|
|
134
|
+
})
|
|
135
|
+
: [];
|
|
136
|
+
// Extract comments
|
|
137
|
+
const commentField = fields["comment"];
|
|
138
|
+
const commentList = (commentField?.["comments"] ?? []);
|
|
139
|
+
const renderedComments = (renderedFields["comment"]?.["comments"] ?? []);
|
|
140
|
+
// Build map of rendered comments by ID for accurate matching
|
|
141
|
+
const renderedMap = new Map();
|
|
142
|
+
for (const r of renderedComments) {
|
|
143
|
+
const rId = String(r["id"] ?? "");
|
|
144
|
+
if (rId)
|
|
145
|
+
renderedMap.set(rId, r);
|
|
146
|
+
}
|
|
147
|
+
const comments = commentList.map((c) => {
|
|
148
|
+
const author = c["author"];
|
|
149
|
+
const commentId = String(c["id"] ?? "");
|
|
150
|
+
const rendered = renderedMap.get(commentId);
|
|
151
|
+
const renderedBody = rendered?.["body"];
|
|
152
|
+
return {
|
|
153
|
+
id: commentId,
|
|
154
|
+
author: extractDisplayName(author) ?? "Unknown",
|
|
155
|
+
body: typeof renderedBody === "string" ? renderedBody : String(c["body"] ?? ""),
|
|
156
|
+
created: parseDate(c["created"]),
|
|
157
|
+
updated: parseDate(c["updated"])
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
// Use rendered description (HTML) if available
|
|
161
|
+
const description = typeof renderedFields["description"] === "string"
|
|
162
|
+
? renderedFields["description"]
|
|
163
|
+
: String(fields["description"] ?? "");
|
|
164
|
+
return {
|
|
165
|
+
key,
|
|
166
|
+
id,
|
|
167
|
+
summary: String(fields["summary"] ?? ""),
|
|
168
|
+
status: extractDisplayName(fields["status"]) ?? "Unknown",
|
|
169
|
+
type: extractDisplayName(fields["issuetype"]) ?? "Unknown",
|
|
170
|
+
priority: extractDisplayName(fields["priority"]),
|
|
171
|
+
assignee: extractDisplayName(fields["assignee"]),
|
|
172
|
+
reporter: extractDisplayName(fields["reporter"]),
|
|
173
|
+
created: parseDate(fields["created"]),
|
|
174
|
+
updated: parseDate(fields["updated"]),
|
|
175
|
+
fixVersions: extractNameArray(fields["fixVersions"]),
|
|
176
|
+
labels: extractNameArray(fields["labels"]),
|
|
177
|
+
components: extractNameArray(fields["components"]),
|
|
178
|
+
description,
|
|
179
|
+
attachments,
|
|
180
|
+
comments,
|
|
181
|
+
url: `${baseUrl}/browse/${key}`
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
const make = Effect.gen(function* () {
|
|
185
|
+
const client = yield* JiraApiClient;
|
|
186
|
+
const siteUrl = yield* SiteUrl;
|
|
187
|
+
const getByKey = (key) => toEffect(client.v3.client.GET("/rest/api/3/issue/{issueIdOrKey}", {
|
|
188
|
+
params: {
|
|
189
|
+
path: { issueIdOrKey: key },
|
|
190
|
+
query: { fields: FIELDS, expand: "renderedFields" }
|
|
191
|
+
}
|
|
192
|
+
})).pipe(Effect.map((result) => mapIssue(result, siteUrl)), Effect.mapError((cause) => new JiraApiError({ message: `Failed to get issue ${key}`, cause })));
|
|
193
|
+
const searchJql = (jql, maxResults, nextPageToken) => toEffect(client.v3.client.GET("/rest/api/3/search/jql", {
|
|
194
|
+
params: {
|
|
195
|
+
query: {
|
|
196
|
+
jql,
|
|
197
|
+
maxResults,
|
|
198
|
+
...(nextPageToken ? { nextPageToken } : {}),
|
|
199
|
+
fields: FIELDS,
|
|
200
|
+
expand: "renderedFields"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
})).pipe(Effect.mapError((cause) => new JiraApiError({ message: "Failed to search issues", cause })));
|
|
204
|
+
const search = (jql, options) => searchJql(jql, options?.maxResults ?? 50).pipe(Effect.map((result) => {
|
|
205
|
+
const issues = result.issues ?? [];
|
|
206
|
+
const mappedIssues = [];
|
|
207
|
+
for (const bean of issues) {
|
|
208
|
+
mappedIssues.push(mapIssue(bean, siteUrl));
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
issues: mappedIssues,
|
|
212
|
+
isLast: result.isLast ?? true,
|
|
213
|
+
nextPageToken: result.nextPageToken ?? null,
|
|
214
|
+
maxResults: options?.maxResults ?? 50
|
|
215
|
+
};
|
|
216
|
+
}));
|
|
217
|
+
const MAX_PAGES = 1000;
|
|
218
|
+
const searchAll = (jql, options) => Effect.gen(function* () {
|
|
219
|
+
const allIssues = [];
|
|
220
|
+
const maxResults = options?.maxResults ?? 100;
|
|
221
|
+
let nextPageToken = undefined;
|
|
222
|
+
let pageCount = 0;
|
|
223
|
+
// Fetch first page
|
|
224
|
+
let result = yield* searchJql(jql, maxResults, nextPageToken);
|
|
225
|
+
let issues = result.issues ?? [];
|
|
226
|
+
pageCount++;
|
|
227
|
+
for (const bean of issues) {
|
|
228
|
+
allIssues.push(mapIssue(bean, siteUrl));
|
|
229
|
+
}
|
|
230
|
+
// Fetch remaining pages with iteration guard
|
|
231
|
+
while (!result.isLast && result.nextPageToken && pageCount < MAX_PAGES) {
|
|
232
|
+
nextPageToken = result.nextPageToken;
|
|
233
|
+
result = yield* searchJql(jql, maxResults, nextPageToken);
|
|
234
|
+
issues = result.issues ?? [];
|
|
235
|
+
pageCount++;
|
|
236
|
+
for (const bean of issues) {
|
|
237
|
+
allIssues.push(mapIssue(bean, siteUrl));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return allIssues;
|
|
241
|
+
});
|
|
242
|
+
return IssueService.of({ getByKey, search, searchAll });
|
|
243
|
+
});
|
|
244
|
+
/**
|
|
245
|
+
* Layer for IssueService.
|
|
246
|
+
*
|
|
247
|
+
* @category Layers
|
|
248
|
+
*/
|
|
249
|
+
export const layer = Layer.effect(IssueService, make);
|
|
250
|
+
//# sourceMappingURL=IssueService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IssueService.js","sourceRoot":"","sources":["../src/IssueService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as CommandExecutor from "@effect/platform/CommandExecutor";
|
|
2
|
+
import type * as Error from "@effect/platform/Error";
|
|
3
|
+
import * as HttpClient from "@effect/platform/HttpClient";
|
|
4
|
+
import { OAuthError } from "@knpkv/atlassian-common/auth";
|
|
5
|
+
import { type FileSystemError, type HomeDirectoryError, type OAuthConfig, type OAuthUser } from "@knpkv/atlassian-common/config";
|
|
6
|
+
import * as Context from "effect/Context";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Layer from "effect/Layer";
|
|
9
|
+
import * as Redacted from "effect/Redacted";
|
|
10
|
+
import type { AuthMissingError } from "./JiraCliError.js";
|
|
11
|
+
/**
|
|
12
|
+
* Options for the login method.
|
|
13
|
+
*
|
|
14
|
+
* @category Types
|
|
15
|
+
*/
|
|
16
|
+
export interface LoginOptions {
|
|
17
|
+
/** Site URL to select (for accounts with multiple sites) */
|
|
18
|
+
readonly siteUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Information about an accessible Jira site.
|
|
22
|
+
*
|
|
23
|
+
* @category Types
|
|
24
|
+
*/
|
|
25
|
+
export interface AccessibleSite {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly url: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* JiraAuth service interface.
|
|
32
|
+
*
|
|
33
|
+
* @category Services
|
|
34
|
+
*/
|
|
35
|
+
export interface JiraAuthService {
|
|
36
|
+
/** Configure OAuth client credentials */
|
|
37
|
+
readonly configure: (config: OAuthConfig) => Effect.Effect<void, FileSystemError | HomeDirectoryError | Error.PlatformError>;
|
|
38
|
+
/** Check if OAuth is configured */
|
|
39
|
+
readonly isConfigured: () => Effect.Effect<boolean, FileSystemError | HomeDirectoryError | Error.PlatformError>;
|
|
40
|
+
/** Start OAuth login flow. Returns list of sites if multiple are available. */
|
|
41
|
+
readonly login: (options?: LoginOptions) => Effect.Effect<ReadonlyArray<AccessibleSite> | void, OAuthError | FileSystemError | HomeDirectoryError | Error.PlatformError>;
|
|
42
|
+
/** Remove stored authentication */
|
|
43
|
+
readonly logout: () => Effect.Effect<void, OAuthError | FileSystemError | HomeDirectoryError | Error.PlatformError>;
|
|
44
|
+
/** Get access token, refreshing if needed */
|
|
45
|
+
readonly getAccessToken: () => Effect.Effect<Redacted.Redacted<string>, AuthMissingError | OAuthError | FileSystemError | HomeDirectoryError | Error.PlatformError>;
|
|
46
|
+
/** Get cloud ID from stored token */
|
|
47
|
+
readonly getCloudId: () => Effect.Effect<string, AuthMissingError | FileSystemError | HomeDirectoryError | Error.PlatformError>;
|
|
48
|
+
/** Get site URL from stored token */
|
|
49
|
+
readonly getSiteUrl: () => Effect.Effect<string, AuthMissingError | FileSystemError | HomeDirectoryError | Error.PlatformError>;
|
|
50
|
+
/** Get current user info from stored token */
|
|
51
|
+
readonly getCurrentUser: () => Effect.Effect<OAuthUser | null, FileSystemError | HomeDirectoryError | Error.PlatformError>;
|
|
52
|
+
/** Check if user is logged in */
|
|
53
|
+
readonly isLoggedIn: () => Effect.Effect<boolean, FileSystemError | HomeDirectoryError | Error.PlatformError>;
|
|
54
|
+
}
|
|
55
|
+
declare const JiraAuth_base: Context.TagClass<JiraAuth, "@knpkv/jira-cli/JiraAuth", JiraAuthService>;
|
|
56
|
+
/**
|
|
57
|
+
* JiraAuth service tag.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* import { Effect } from "effect"
|
|
62
|
+
* import { JiraAuth } from "@knpkv/jira-cli/JiraAuth"
|
|
63
|
+
*
|
|
64
|
+
* Effect.gen(function* () {
|
|
65
|
+
* const auth = yield* JiraAuth
|
|
66
|
+
* const isLoggedIn = yield* auth.isLoggedIn()
|
|
67
|
+
* if (!isLoggedIn) {
|
|
68
|
+
* yield* auth.login()
|
|
69
|
+
* }
|
|
70
|
+
* })
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @category Services
|
|
74
|
+
*/
|
|
75
|
+
export declare class JiraAuth extends JiraAuth_base {
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Layer for JiraAuth service.
|
|
79
|
+
*
|
|
80
|
+
* @category Layers
|
|
81
|
+
*/
|
|
82
|
+
export declare const layer: Layer.Layer<JiraAuth, never, HttpClient.HttpClient | CommandExecutor.CommandExecutor>;
|
|
83
|
+
export {};
|
|
84
|
+
//# sourceMappingURL=JiraAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JiraAuth.d.ts","sourceRoot":"","sources":["../src/JiraAuth.ts"],"names":[],"mappings":"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"}
|