@specship/specship-win32-arm64 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dist/bin/specship.js +180 -1
- package/lib/dist/bin/specship.js.map +1 -1
- package/lib/dist/jira/auth.d.ts +17 -0
- package/lib/dist/jira/auth.d.ts.map +1 -0
- package/lib/dist/jira/auth.js +33 -0
- package/lib/dist/jira/auth.js.map +1 -0
- package/lib/dist/jira/client.d.ts +120 -0
- package/lib/dist/jira/client.d.ts.map +1 -0
- package/lib/dist/jira/client.js +316 -0
- package/lib/dist/jira/client.js.map +1 -0
- package/lib/dist/jira/config.d.ts +45 -0
- package/lib/dist/jira/config.d.ts.map +1 -0
- package/lib/dist/jira/config.js +178 -0
- package/lib/dist/jira/config.js.map +1 -0
- package/lib/dist/jira/index.d.ts +8 -0
- package/lib/dist/jira/index.d.ts.map +1 -0
- package/lib/dist/jira/index.js +24 -0
- package/lib/dist/jira/index.js.map +1 -0
- package/lib/dist/jira/pull-request.d.ts +72 -0
- package/lib/dist/jira/pull-request.d.ts.map +1 -0
- package/lib/dist/jira/pull-request.js +127 -0
- package/lib/dist/jira/pull-request.js.map +1 -0
- package/lib/dist/jira/spec-generator.d.ts +34 -0
- package/lib/dist/jira/spec-generator.d.ts.map +1 -0
- package/lib/dist/jira/spec-generator.js +138 -0
- package/lib/dist/jira/spec-generator.js.map +1 -0
- package/lib/dist/jira/spec-writer.d.ts +36 -0
- package/lib/dist/jira/spec-writer.d.ts.map +1 -0
- package/lib/dist/jira/spec-writer.js +143 -0
- package/lib/dist/jira/spec-writer.js.map +1 -0
- package/lib/dist/jira/types.d.ts +174 -0
- package/lib/dist/jira/types.d.ts.map +1 -0
- package/lib/dist/jira/types.js +60 -0
- package/lib/dist/jira/types.js.map +1 -0
- package/lib/dist/mcp/jira-tools.d.ts +234 -0
- package/lib/dist/mcp/jira-tools.d.ts.map +1 -0
- package/lib/dist/mcp/jira-tools.js +787 -0
- package/lib/dist/mcp/jira-tools.js.map +1 -0
- package/lib/dist/mcp/server-instructions.d.ts +1 -1
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +10 -0
- package/lib/dist/mcp/server-instructions.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +32 -0
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/server/cli.js +1 -1
- package/lib/dist/server/routes/sse.js +1 -1
- package/lib/dist/server/server.js +1 -1
- package/lib/dist/statusline/index.d.ts +19 -0
- package/lib/dist/statusline/index.d.ts.map +1 -1
- package/lib/dist/statusline/index.js +96 -3
- package/lib/dist/statusline/index.js.map +1 -1
- package/lib/dist/statusline/render.d.ts +24 -0
- package/lib/dist/statusline/render.d.ts.map +1 -1
- package/lib/dist/statusline/render.js +26 -5
- package/lib/dist/statusline/render.js.map +1 -1
- package/lib/dist/workflows/executor.d.ts +25 -1
- package/lib/dist/workflows/executor.d.ts.map +1 -1
- package/lib/dist/workflows/executor.js +28 -3
- package/lib/dist/workflows/executor.js.map +1 -1
- package/lib/node_modules/.package-lock.json +11 -20
- package/lib/node_modules/ajv/node_modules/fast-uri/index.js +1 -1
- package/lib/node_modules/ajv/node_modules/fast-uri/package.json +1 -1
- package/lib/node_modules/ajv/node_modules/fast-uri/test/security.test.js +28 -0
- package/lib/node_modules/toad-cache/README.md +10 -9
- package/lib/node_modules/toad-cache/dist/toad-cache.cjs +139 -139
- package/lib/node_modules/toad-cache/dist/toad-cache.mjs +136 -140
- package/lib/node_modules/toad-cache/package.json +8 -8
- package/lib/node_modules/toad-cache/toad-cache.d.cts +20 -14
- package/lib/node_modules/toad-cache/toad-cache.d.ts +18 -14
- package/lib/node_modules/yaml/README.md +14 -31
- package/lib/node_modules/yaml/browser/dist/compose/compose-collection.js +3 -3
- package/lib/node_modules/yaml/browser/dist/compose/compose-doc.js +0 -1
- package/lib/node_modules/yaml/browser/dist/compose/compose-node.js +4 -21
- package/lib/node_modules/yaml/browser/dist/compose/compose-scalar.js +8 -14
- package/lib/node_modules/yaml/browser/dist/compose/composer.js +2 -4
- package/lib/node_modules/yaml/browser/dist/compose/resolve-block-map.js +0 -2
- package/lib/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +1 -3
- package/lib/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +2 -8
- package/lib/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +4 -6
- package/lib/node_modules/yaml/browser/dist/compose/resolve-props.js +5 -3
- package/lib/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +2 -1
- package/lib/node_modules/yaml/browser/dist/compose/util-map-includes.js +5 -1
- package/lib/node_modules/yaml/browser/dist/doc/Document.js +2 -3
- package/lib/node_modules/yaml/browser/dist/doc/anchors.js +2 -1
- package/lib/node_modules/yaml/browser/dist/doc/applyReviver.js +0 -1
- package/lib/node_modules/yaml/browser/dist/doc/createNode.js +2 -1
- package/lib/node_modules/yaml/browser/dist/errors.js +1 -1
- package/lib/node_modules/yaml/browser/dist/log.js +6 -1
- package/lib/node_modules/yaml/browser/dist/node_modules/tslib/tslib.es6.js +21 -0
- package/lib/node_modules/yaml/browser/dist/nodes/Alias.js +12 -27
- package/lib/node_modules/yaml/browser/dist/nodes/YAMLMap.js +1 -1
- package/lib/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +49 -8
- package/lib/node_modules/yaml/browser/dist/parse/lexer.js +24 -28
- package/lib/node_modules/yaml/browser/dist/parse/parser.js +8 -31
- package/lib/node_modules/yaml/browser/dist/public-api.js +0 -3
- package/lib/node_modules/yaml/browser/dist/schema/Schema.js +2 -1
- package/lib/node_modules/yaml/browser/dist/schema/json/schema.js +1 -1
- package/lib/node_modules/yaml/browser/dist/schema/tags.js +16 -29
- package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +13 -5
- package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +1 -1
- package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +0 -2
- package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +1 -1
- package/lib/node_modules/yaml/browser/dist/stringify/stringify.js +5 -10
- package/lib/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +3 -13
- package/lib/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +2 -3
- package/lib/node_modules/yaml/browser/dist/stringify/stringifyPair.js +1 -1
- package/lib/node_modules/yaml/browser/dist/stringify/stringifyString.js +16 -24
- package/lib/node_modules/yaml/browser/dist/util.js +1 -1
- package/lib/node_modules/yaml/dist/cli.d.ts +1 -1
- package/lib/node_modules/yaml/dist/cli.mjs +3 -5
- package/lib/node_modules/yaml/dist/compose/compose-collection.d.ts +4 -4
- package/lib/node_modules/yaml/dist/compose/compose-collection.js +3 -3
- package/lib/node_modules/yaml/dist/compose/compose-doc.d.ts +6 -6
- package/lib/node_modules/yaml/dist/compose/compose-doc.js +0 -1
- package/lib/node_modules/yaml/dist/compose/compose-node.d.ts +7 -8
- package/lib/node_modules/yaml/dist/compose/compose-node.js +4 -21
- package/lib/node_modules/yaml/dist/compose/compose-scalar.d.ts +4 -4
- package/lib/node_modules/yaml/dist/compose/compose-scalar.js +7 -13
- package/lib/node_modules/yaml/dist/compose/composer.d.ts +6 -7
- package/lib/node_modules/yaml/dist/compose/composer.js +3 -6
- package/lib/node_modules/yaml/dist/compose/resolve-block-map.d.ts +5 -5
- package/lib/node_modules/yaml/dist/compose/resolve-block-map.js +0 -2
- package/lib/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +5 -5
- package/lib/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +5 -5
- package/lib/node_modules/yaml/dist/compose/resolve-block-seq.js +1 -3
- package/lib/node_modules/yaml/dist/compose/resolve-end.d.ts +2 -2
- package/lib/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -7
- package/lib/node_modules/yaml/dist/compose/resolve-flow-collection.js +2 -8
- package/lib/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +4 -4
- package/lib/node_modules/yaml/dist/compose/resolve-flow-scalar.js +4 -6
- package/lib/node_modules/yaml/dist/compose/resolve-props.d.ts +2 -2
- package/lib/node_modules/yaml/dist/compose/resolve-props.js +5 -3
- package/lib/node_modules/yaml/dist/compose/util-contains-newline.d.ts +1 -1
- package/lib/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +1 -1
- package/lib/node_modules/yaml/dist/compose/util-empty-scalar-position.js +2 -1
- package/lib/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +2 -2
- package/lib/node_modules/yaml/dist/compose/util-map-includes.d.ts +3 -3
- package/lib/node_modules/yaml/dist/compose/util-map-includes.js +5 -1
- package/lib/node_modules/yaml/dist/doc/Document.d.ts +11 -11
- package/lib/node_modules/yaml/dist/doc/Document.js +2 -3
- package/lib/node_modules/yaml/dist/doc/anchors.d.ts +2 -2
- package/lib/node_modules/yaml/dist/doc/anchors.js +2 -1
- package/lib/node_modules/yaml/dist/doc/applyReviver.js +0 -1
- package/lib/node_modules/yaml/dist/doc/createNode.d.ts +4 -4
- package/lib/node_modules/yaml/dist/doc/createNode.js +2 -1
- package/lib/node_modules/yaml/dist/doc/directives.d.ts +1 -1
- package/lib/node_modules/yaml/dist/errors.d.ts +1 -1
- package/lib/node_modules/yaml/dist/errors.js +1 -1
- package/lib/node_modules/yaml/dist/index.d.ts +18 -21
- package/lib/node_modules/yaml/dist/log.js +4 -4
- package/lib/node_modules/yaml/dist/nodes/Alias.d.ts +9 -10
- package/lib/node_modules/yaml/dist/nodes/Alias.js +11 -26
- package/lib/node_modules/yaml/dist/nodes/Collection.d.ts +4 -4
- package/lib/node_modules/yaml/dist/nodes/Node.d.ts +9 -16
- package/lib/node_modules/yaml/dist/nodes/Pair.d.ts +8 -9
- package/lib/node_modules/yaml/dist/nodes/Scalar.d.ts +4 -9
- package/lib/node_modules/yaml/dist/nodes/YAMLMap.d.ts +10 -10
- package/lib/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +10 -10
- package/lib/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +3 -3
- package/lib/node_modules/yaml/dist/nodes/addPairToJSMap.js +48 -7
- package/lib/node_modules/yaml/dist/nodes/identity.d.ts +7 -7
- package/lib/node_modules/yaml/dist/nodes/toJS.d.ts +2 -4
- package/lib/node_modules/yaml/dist/options.d.ts +10 -22
- package/lib/node_modules/yaml/dist/parse/cst-scalar.d.ts +4 -4
- package/lib/node_modules/yaml/dist/parse/cst-stringify.d.ts +1 -1
- package/lib/node_modules/yaml/dist/parse/cst-visit.d.ts +2 -2
- package/lib/node_modules/yaml/dist/parse/cst.d.ts +3 -4
- package/lib/node_modules/yaml/dist/parse/lexer.d.ts +1 -1
- package/lib/node_modules/yaml/dist/parse/lexer.js +24 -28
- package/lib/node_modules/yaml/dist/parse/parser.d.ts +4 -4
- package/lib/node_modules/yaml/dist/parse/parser.js +9 -33
- package/lib/node_modules/yaml/dist/public-api.d.ts +5 -6
- package/lib/node_modules/yaml/dist/public-api.js +0 -3
- package/lib/node_modules/yaml/dist/schema/Schema.d.ts +5 -4
- package/lib/node_modules/yaml/dist/schema/Schema.js +2 -1
- package/lib/node_modules/yaml/dist/schema/common/map.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/common/null.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/common/seq.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/common/string.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/core/bool.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/core/float.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/core/int.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/core/schema.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/json/schema.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/json/schema.js +1 -1
- package/lib/node_modules/yaml/dist/schema/tags.d.ts +3 -11
- package/lib/node_modules/yaml/dist/schema/tags.js +13 -26
- package/lib/node_modules/yaml/dist/schema/types.d.ts +12 -14
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/binary.js +7 -9
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +17 -11
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +7 -7
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/schema.js +0 -2
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +9 -9
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +1 -1
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +1 -1
- package/lib/node_modules/yaml/dist/stringify/stringify.d.ts +3 -3
- package/lib/node_modules/yaml/dist/stringify/stringify.js +5 -10
- package/lib/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +2 -2
- package/lib/node_modules/yaml/dist/stringify/stringifyCollection.js +3 -13
- package/lib/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +3 -3
- package/lib/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +1 -1
- package/lib/node_modules/yaml/dist/stringify/stringifyNumber.js +2 -3
- package/lib/node_modules/yaml/dist/stringify/stringifyPair.d.ts +2 -2
- package/lib/node_modules/yaml/dist/stringify/stringifyPair.js +1 -1
- package/lib/node_modules/yaml/dist/stringify/stringifyString.d.ts +2 -2
- package/lib/node_modules/yaml/dist/stringify/stringifyString.js +15 -23
- package/lib/node_modules/yaml/dist/util.d.ts +12 -16
- package/lib/node_modules/yaml/dist/util.js +2 -2
- package/lib/node_modules/yaml/dist/visit.d.ts +7 -7
- package/lib/node_modules/yaml/package.json +14 -13
- package/lib/package.json +5 -6
- package/package.json +1 -1
- package/lib/node_modules/jsonc-parser/CHANGELOG.md +0 -76
- package/lib/node_modules/jsonc-parser/LICENSE.md +0 -21
- package/lib/node_modules/jsonc-parser/README.md +0 -364
- package/lib/node_modules/jsonc-parser/SECURITY.md +0 -41
- package/lib/node_modules/jsonc-parser/lib/esm/impl/edit.js +0 -185
- package/lib/node_modules/jsonc-parser/lib/esm/impl/format.js +0 -261
- package/lib/node_modules/jsonc-parser/lib/esm/impl/parser.js +0 -659
- package/lib/node_modules/jsonc-parser/lib/esm/impl/scanner.js +0 -443
- package/lib/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +0 -29
- package/lib/node_modules/jsonc-parser/lib/esm/main.d.ts +0 -351
- package/lib/node_modules/jsonc-parser/lib/esm/main.js +0 -178
- package/lib/node_modules/jsonc-parser/lib/umd/impl/edit.js +0 -201
- package/lib/node_modules/jsonc-parser/lib/umd/impl/format.js +0 -275
- package/lib/node_modules/jsonc-parser/lib/umd/impl/parser.js +0 -682
- package/lib/node_modules/jsonc-parser/lib/umd/impl/scanner.js +0 -456
- package/lib/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +0 -42
- package/lib/node_modules/jsonc-parser/lib/umd/main.d.ts +0 -351
- package/lib/node_modules/jsonc-parser/lib/umd/main.js +0 -194
- package/lib/node_modules/jsonc-parser/package.json +0 -37
- package/lib/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +0 -67
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +0 -9
- package/lib/node_modules/yaml/dist/schema/yaml-1.1/merge.js +0 -71
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal JIRA REST client (REQ-JIRA-001, A3).
|
|
3
|
+
*
|
|
4
|
+
* Wraps `fetch` with the base URL + auth header. `testConnection()` probes
|
|
5
|
+
* `/rest/api/2/myself` (available on both Cloud and Data Center);
|
|
6
|
+
* `listMyIssues()` (REQ-JIRA-002) searches the current user's issues. Both
|
|
7
|
+
* go through the private `request()` helper so the security guards below
|
|
8
|
+
* apply to every credentialed call.
|
|
9
|
+
*
|
|
10
|
+
* SECURITY (foundation for REQ-JIRA-009):
|
|
11
|
+
* - `redirect: 'manual'` — we never auto-follow a redirect, so the
|
|
12
|
+
* `Authorization` header can't be replayed to a different host.
|
|
13
|
+
* - We only ever talk to the configured base URL's host; a cross-host
|
|
14
|
+
* (or any) redirect is refused, not followed.
|
|
15
|
+
* - No thrown error or result field ever contains the credential.
|
|
16
|
+
*/
|
|
17
|
+
import { JiraCredentials, JiraConnectionResult, JiraIssueListResult, JiraIssueResult, JiraTransition, JiraTransitionResult } from './types';
|
|
18
|
+
export declare class JiraClient {
|
|
19
|
+
private readonly baseUrl;
|
|
20
|
+
private readonly host;
|
|
21
|
+
private readonly authHeader;
|
|
22
|
+
private readonly deployment;
|
|
23
|
+
constructor(creds: JiraCredentials);
|
|
24
|
+
/**
|
|
25
|
+
* Probe the instance's `/myself` endpoint.
|
|
26
|
+
* - 200 → `{ ok: true, accountId, displayName }`.
|
|
27
|
+
* - 401 / 403 → `JiraAuthError`.
|
|
28
|
+
* - 3xx redirect → `JiraConfigError` (refused, never followed).
|
|
29
|
+
* - network / DNS failure → `JiraConfigError`.
|
|
30
|
+
*/
|
|
31
|
+
testConnection(): Promise<JiraConnectionResult>;
|
|
32
|
+
/**
|
|
33
|
+
* List the issues assigned to the authenticated user (REQ-JIRA-002).
|
|
34
|
+
* Identity comes from the token via JQL `currentUser()` — the user never
|
|
35
|
+
* types their own name (A1). An optional `project` narrows the search
|
|
36
|
+
* (A2); the value is quote-escaped to prevent JQL injection. Ordered
|
|
37
|
+
* most-actionable-first (recently updated). Bounded by `MAX_ISSUE_RESULTS`.
|
|
38
|
+
*
|
|
39
|
+
* - 200 → `{ ok: true, issues }`; an empty list is a valid success (A3).
|
|
40
|
+
* - 401 / 403 → `JiraAuthError`; redirect / network / non-200 →
|
|
41
|
+
* `JiraConfigError` (A4) — never a partial or fabricated list.
|
|
42
|
+
*/
|
|
43
|
+
listMyIssues(opts?: {
|
|
44
|
+
project?: string;
|
|
45
|
+
}): Promise<JiraIssueListResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Fetch a single issue by its key (REQ-JIRA-003). Identity/authorization
|
|
48
|
+
* ride the token — a key the user can't see is indistinguishable from a
|
|
49
|
+
* missing one and both surface as `JiraNotFoundError` (A2). The key is
|
|
50
|
+
* URL-encoded before it enters the path so a slash/space can't traverse or
|
|
51
|
+
* inject into the request line.
|
|
52
|
+
*
|
|
53
|
+
* - 200 → `{ ok: true, issue }` with summary/description/status/type/subtasks.
|
|
54
|
+
* - 404 → `JiraNotFoundError` (no such issue, or no access).
|
|
55
|
+
* - 401 / 403 → `JiraAuthError`; redirect / network / non-200 →
|
|
56
|
+
* `JiraConfigError`. Never a partial or fabricated issue.
|
|
57
|
+
*/
|
|
58
|
+
getIssue(key: string): Promise<JiraIssueResult>;
|
|
59
|
+
/**
|
|
60
|
+
* List the transitions the issue's current workflow state offers
|
|
61
|
+
* (REQ-JIRA-007). `GET /issue/{key}/transitions`. Used to resolve a
|
|
62
|
+
* configured transition name/id to an executable id before writing.
|
|
63
|
+
*/
|
|
64
|
+
listTransitions(key: string): Promise<JiraTransition[]>;
|
|
65
|
+
/**
|
|
66
|
+
* Drive the issue toward a target state (REQ-JIRA-007). `nameOrId` is
|
|
67
|
+
* matched against the issue's available transitions by exact id first, then
|
|
68
|
+
* case-insensitively by name. On a match it POSTs `/transitions`. When NO
|
|
69
|
+
* transition matches — because this project's workflow doesn't offer it —
|
|
70
|
+
* it returns `{ ok, skipped, reason }` and NEVER throws (A3): a missing
|
|
71
|
+
* transition is an expected, recoverable state, and the caller still
|
|
72
|
+
* comments the PR link and reports the skip. Auth/network faults on the
|
|
73
|
+
* write still throw like any other credentialed call.
|
|
74
|
+
*/
|
|
75
|
+
transitionIssue(key: string, nameOrId: string): Promise<JiraTransitionResult>;
|
|
76
|
+
/**
|
|
77
|
+
* Assign the issue (REQ-JIRA-007). Cloud keys assignment by `accountId`;
|
|
78
|
+
* Data Center by `name`. `PUT /issue/{key}/assignee`. A 204 (no body) is the
|
|
79
|
+
* success shape — the `write` helper tolerates an empty response.
|
|
80
|
+
*/
|
|
81
|
+
assignIssue(key: string, accountId: string): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Add a comment to the issue (REQ-JIRA-007) — used to record the PR link.
|
|
84
|
+
* `POST /issue/{key}/comment`. api/2 takes a plain-string body.
|
|
85
|
+
*
|
|
86
|
+
* SECURITY: the caller only ever passes the public PR URL / issue key here,
|
|
87
|
+
* never a credential (REQ-JIRA-009).
|
|
88
|
+
*/
|
|
89
|
+
addComment(key: string, body: string): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Shared, credentialed GET against the configured host. Delegates to
|
|
92
|
+
* `send()` for the security guards, then parses JSON. Returns the parsed
|
|
93
|
+
* JSON body on success; a non-JSON body is a `JiraConfigError`.
|
|
94
|
+
*/
|
|
95
|
+
private request;
|
|
96
|
+
/**
|
|
97
|
+
* Shared, credentialed write (POST/PUT) against the configured host
|
|
98
|
+
* (REQ-JIRA-007). Carries EVERY guard `request()` does via `send()` —
|
|
99
|
+
* `redirect: 'manual'`, host-lock, 401/403 → auth, 404 → not-found,
|
|
100
|
+
* non-2xx → config, credential-free messages. A JSON body is sent with
|
|
101
|
+
* `Content-Type: application/json`; a success with no/empty body (204, the
|
|
102
|
+
* common transition/assignee shape) resolves to `null` rather than erroring
|
|
103
|
+
* on the missing JSON.
|
|
104
|
+
*/
|
|
105
|
+
private write;
|
|
106
|
+
/**
|
|
107
|
+
* The single fetch + guard chokepoint shared by `request` and `write`. Every
|
|
108
|
+
* security guard lives here so no call path can skip one:
|
|
109
|
+
* - `redirect: 'manual'` — the `Authorization` header is never replayed
|
|
110
|
+
* across a redirect; any 3xx / opaqueredirect is refused, not followed.
|
|
111
|
+
* - only ever talks to the configured host.
|
|
112
|
+
* - 401 / 403 → `JiraAuthError`; 404 → `JiraNotFoundError`; network /
|
|
113
|
+
* non-2xx → `JiraConfigError`.
|
|
114
|
+
* - No thrown message ever contains the credential — only the host.
|
|
115
|
+
*
|
|
116
|
+
* Returns the raw `Response` on a 2xx; the caller parses the body.
|
|
117
|
+
*/
|
|
118
|
+
private send;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/jira/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EACL,eAAe,EACf,oBAAoB,EAEpB,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,oBAAoB,EAIrB,MAAM,SAAS,CAAC;AAKjB,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgC;gBAE/C,KAAK,EAAE,eAAe;IAclC;;;;;;OAMG;IACG,cAAc,IAAI,OAAO,CAAC,oBAAoB,CAAC;IAUrD;;;;;;;;;;OAUG;IACG,YAAY,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA6B7E;;;;;;;;;;;OAWG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IA2CrD;;;;OAIG;IACG,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAY7D;;;;;;;;;OASG;IACG,eAAe,CACnB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,CAAC;IAmChC;;;;OAIG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBhE;;;;;;OAMG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW1D;;;;OAIG;YACW,OAAO;IAWrB;;;;;;;;OAQG;YACW,KAAK;IAgBnB;;;;;;;;;;;OAWG;YACW,IAAI;CAoEnB"}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal JIRA REST client (REQ-JIRA-001, A3).
|
|
4
|
+
*
|
|
5
|
+
* Wraps `fetch` with the base URL + auth header. `testConnection()` probes
|
|
6
|
+
* `/rest/api/2/myself` (available on both Cloud and Data Center);
|
|
7
|
+
* `listMyIssues()` (REQ-JIRA-002) searches the current user's issues. Both
|
|
8
|
+
* go through the private `request()` helper so the security guards below
|
|
9
|
+
* apply to every credentialed call.
|
|
10
|
+
*
|
|
11
|
+
* SECURITY (foundation for REQ-JIRA-009):
|
|
12
|
+
* - `redirect: 'manual'` — we never auto-follow a redirect, so the
|
|
13
|
+
* `Authorization` header can't be replayed to a different host.
|
|
14
|
+
* - We only ever talk to the configured base URL's host; a cross-host
|
|
15
|
+
* (or any) redirect is refused, not followed.
|
|
16
|
+
* - No thrown error or result field ever contains the credential.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.JiraClient = void 0;
|
|
20
|
+
const auth_1 = require("./auth");
|
|
21
|
+
const types_1 = require("./types");
|
|
22
|
+
/** Upper bound on issues fetched in one list call — never unbounded. */
|
|
23
|
+
const MAX_ISSUE_RESULTS = 50;
|
|
24
|
+
class JiraClient {
|
|
25
|
+
baseUrl;
|
|
26
|
+
host;
|
|
27
|
+
authHeader;
|
|
28
|
+
deployment;
|
|
29
|
+
constructor(creds) {
|
|
30
|
+
// Normalize: strip trailing slashes so path joins are clean.
|
|
31
|
+
this.baseUrl = creds.baseUrl.replace(/\/+$/, '');
|
|
32
|
+
try {
|
|
33
|
+
this.host = new URL(this.baseUrl).host;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new types_1.JiraConfigError(`Invalid JIRA base URL: ${creds.baseUrl}`);
|
|
37
|
+
}
|
|
38
|
+
this.authHeader = (0, auth_1.buildAuthHeader)(creds);
|
|
39
|
+
this.deployment = creds.deployment;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Probe the instance's `/myself` endpoint.
|
|
43
|
+
* - 200 → `{ ok: true, accountId, displayName }`.
|
|
44
|
+
* - 401 / 403 → `JiraAuthError`.
|
|
45
|
+
* - 3xx redirect → `JiraConfigError` (refused, never followed).
|
|
46
|
+
* - network / DNS failure → `JiraConfigError`.
|
|
47
|
+
*/
|
|
48
|
+
async testConnection() {
|
|
49
|
+
const body = await this.request('/rest/api/2/myself');
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
// Cloud → accountId; Data Center → key/name. Prefer the most stable.
|
|
53
|
+
accountId: body?.accountId ?? body?.key ?? body?.name,
|
|
54
|
+
displayName: body?.displayName ?? body?.name,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* List the issues assigned to the authenticated user (REQ-JIRA-002).
|
|
59
|
+
* Identity comes from the token via JQL `currentUser()` — the user never
|
|
60
|
+
* types their own name (A1). An optional `project` narrows the search
|
|
61
|
+
* (A2); the value is quote-escaped to prevent JQL injection. Ordered
|
|
62
|
+
* most-actionable-first (recently updated). Bounded by `MAX_ISSUE_RESULTS`.
|
|
63
|
+
*
|
|
64
|
+
* - 200 → `{ ok: true, issues }`; an empty list is a valid success (A3).
|
|
65
|
+
* - 401 / 403 → `JiraAuthError`; redirect / network / non-200 →
|
|
66
|
+
* `JiraConfigError` (A4) — never a partial or fabricated list.
|
|
67
|
+
*/
|
|
68
|
+
async listMyIssues(opts) {
|
|
69
|
+
let jql = 'assignee = currentUser()';
|
|
70
|
+
if (opts?.project && opts.project.trim()) {
|
|
71
|
+
// Quote-escape the project so an embedded quote can't break out of the
|
|
72
|
+
// JQL string literal (injection guard). JQL escapes `"` as `\"`.
|
|
73
|
+
const escaped = opts.project.trim().replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
74
|
+
jql = `project = "${escaped}" AND ${jql}`;
|
|
75
|
+
}
|
|
76
|
+
jql += ' ORDER BY updated DESC';
|
|
77
|
+
const params = new URLSearchParams({
|
|
78
|
+
jql,
|
|
79
|
+
fields: 'summary,status,issuetype',
|
|
80
|
+
maxResults: String(MAX_ISSUE_RESULTS),
|
|
81
|
+
});
|
|
82
|
+
const body = await this.request(`/rest/api/2/search?${params.toString()}`);
|
|
83
|
+
const rawIssues = Array.isArray(body?.issues) ? body.issues : [];
|
|
84
|
+
const issues = rawIssues.map(issue => ({
|
|
85
|
+
key: String(issue?.key ?? ''),
|
|
86
|
+
id: String(issue?.id ?? ''),
|
|
87
|
+
summary: String(issue?.fields?.summary ?? ''),
|
|
88
|
+
status: String(issue?.fields?.status?.name ?? ''),
|
|
89
|
+
issueType: String(issue?.fields?.issuetype?.name ?? ''),
|
|
90
|
+
}));
|
|
91
|
+
return { ok: true, issues };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Fetch a single issue by its key (REQ-JIRA-003). Identity/authorization
|
|
95
|
+
* ride the token — a key the user can't see is indistinguishable from a
|
|
96
|
+
* missing one and both surface as `JiraNotFoundError` (A2). The key is
|
|
97
|
+
* URL-encoded before it enters the path so a slash/space can't traverse or
|
|
98
|
+
* inject into the request line.
|
|
99
|
+
*
|
|
100
|
+
* - 200 → `{ ok: true, issue }` with summary/description/status/type/subtasks.
|
|
101
|
+
* - 404 → `JiraNotFoundError` (no such issue, or no access).
|
|
102
|
+
* - 401 / 403 → `JiraAuthError`; redirect / network / non-200 →
|
|
103
|
+
* `JiraConfigError`. Never a partial or fabricated issue.
|
|
104
|
+
*/
|
|
105
|
+
async getIssue(key) {
|
|
106
|
+
const trimmed = (key ?? '').trim();
|
|
107
|
+
if (!trimmed) {
|
|
108
|
+
throw new types_1.JiraConfigError('An issue key is required (e.g., "PROJ-123").');
|
|
109
|
+
}
|
|
110
|
+
// URL-encode the key: a slash or space in the key must not traverse the
|
|
111
|
+
// path or split the request — encode it into a single path segment.
|
|
112
|
+
const params = new URLSearchParams({
|
|
113
|
+
fields: 'summary,description,status,issuetype,subtasks',
|
|
114
|
+
});
|
|
115
|
+
const body = await this.request(`/rest/api/2/issue/${encodeURIComponent(trimmed)}?${params.toString()}`);
|
|
116
|
+
const fields = body?.fields ?? {};
|
|
117
|
+
// api/2 returns `description` as a plain string. Guard the non-string case
|
|
118
|
+
// (null → no body; an ADF object under a future api/3 → not `[object
|
|
119
|
+
// Object]`) by treating anything non-string as empty.
|
|
120
|
+
const description = typeof fields.description === 'string' ? fields.description : '';
|
|
121
|
+
const rawSubtasks = Array.isArray(fields.subtasks)
|
|
122
|
+
? fields.subtasks
|
|
123
|
+
: [];
|
|
124
|
+
const subtasks = rawSubtasks.map(st => ({
|
|
125
|
+
key: String(st?.key ?? ''),
|
|
126
|
+
summary: String(st?.fields?.summary ?? ''),
|
|
127
|
+
status: String(st?.fields?.status?.name ?? ''),
|
|
128
|
+
}));
|
|
129
|
+
const issue = {
|
|
130
|
+
key: String(body?.key ?? trimmed),
|
|
131
|
+
id: String(body?.id ?? ''),
|
|
132
|
+
summary: String(fields.summary ?? ''),
|
|
133
|
+
status: String(fields.status?.name ?? ''),
|
|
134
|
+
issueType: String(fields.issuetype?.name ?? ''),
|
|
135
|
+
description,
|
|
136
|
+
subtasks,
|
|
137
|
+
};
|
|
138
|
+
return { ok: true, issue };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* List the transitions the issue's current workflow state offers
|
|
142
|
+
* (REQ-JIRA-007). `GET /issue/{key}/transitions`. Used to resolve a
|
|
143
|
+
* configured transition name/id to an executable id before writing.
|
|
144
|
+
*/
|
|
145
|
+
async listTransitions(key) {
|
|
146
|
+
const trimmed = (key ?? '').trim();
|
|
147
|
+
if (!trimmed) {
|
|
148
|
+
throw new types_1.JiraConfigError('An issue key is required (e.g., "PROJ-123").');
|
|
149
|
+
}
|
|
150
|
+
const body = await this.request(`/rest/api/2/issue/${encodeURIComponent(trimmed)}/transitions`);
|
|
151
|
+
const raw = Array.isArray(body?.transitions) ? body.transitions : [];
|
|
152
|
+
return raw.map(t => ({ id: String(t?.id ?? ''), name: String(t?.name ?? '') }));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Drive the issue toward a target state (REQ-JIRA-007). `nameOrId` is
|
|
156
|
+
* matched against the issue's available transitions by exact id first, then
|
|
157
|
+
* case-insensitively by name. On a match it POSTs `/transitions`. When NO
|
|
158
|
+
* transition matches — because this project's workflow doesn't offer it —
|
|
159
|
+
* it returns `{ ok, skipped, reason }` and NEVER throws (A3): a missing
|
|
160
|
+
* transition is an expected, recoverable state, and the caller still
|
|
161
|
+
* comments the PR link and reports the skip. Auth/network faults on the
|
|
162
|
+
* write still throw like any other credentialed call.
|
|
163
|
+
*/
|
|
164
|
+
async transitionIssue(key, nameOrId) {
|
|
165
|
+
const trimmed = (key ?? '').trim();
|
|
166
|
+
if (!trimmed) {
|
|
167
|
+
throw new types_1.JiraConfigError('An issue key is required (e.g., "PROJ-123").');
|
|
168
|
+
}
|
|
169
|
+
const target = (nameOrId ?? '').trim();
|
|
170
|
+
if (!target) {
|
|
171
|
+
return {
|
|
172
|
+
ok: true,
|
|
173
|
+
skipped: nameOrId,
|
|
174
|
+
reason: 'no transition name/id was configured',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const available = await this.listTransitions(trimmed);
|
|
178
|
+
const wanted = target.toLowerCase();
|
|
179
|
+
const match = available.find(t => t.id === target || t.name.toLowerCase() === wanted);
|
|
180
|
+
if (!match) {
|
|
181
|
+
const names = available.map(t => t.name).join(', ') || 'none';
|
|
182
|
+
return {
|
|
183
|
+
ok: true,
|
|
184
|
+
skipped: target,
|
|
185
|
+
reason: `no "${target}" transition on this issue's workflow (available: ${names})`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
await this.write(`/rest/api/2/issue/${encodeURIComponent(trimmed)}/transitions`, { transition: { id: match.id } });
|
|
189
|
+
return { ok: true, transitioned: match.name };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Assign the issue (REQ-JIRA-007). Cloud keys assignment by `accountId`;
|
|
193
|
+
* Data Center by `name`. `PUT /issue/{key}/assignee`. A 204 (no body) is the
|
|
194
|
+
* success shape — the `write` helper tolerates an empty response.
|
|
195
|
+
*/
|
|
196
|
+
async assignIssue(key, accountId) {
|
|
197
|
+
const trimmed = (key ?? '').trim();
|
|
198
|
+
if (!trimmed) {
|
|
199
|
+
throw new types_1.JiraConfigError('An issue key is required (e.g., "PROJ-123").');
|
|
200
|
+
}
|
|
201
|
+
const body = this.deployment === 'datacenter'
|
|
202
|
+
? { name: accountId }
|
|
203
|
+
: { accountId };
|
|
204
|
+
await this.write(`/rest/api/2/issue/${encodeURIComponent(trimmed)}/assignee`, body, 'PUT');
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Add a comment to the issue (REQ-JIRA-007) — used to record the PR link.
|
|
208
|
+
* `POST /issue/{key}/comment`. api/2 takes a plain-string body.
|
|
209
|
+
*
|
|
210
|
+
* SECURITY: the caller only ever passes the public PR URL / issue key here,
|
|
211
|
+
* never a credential (REQ-JIRA-009).
|
|
212
|
+
*/
|
|
213
|
+
async addComment(key, body) {
|
|
214
|
+
const trimmed = (key ?? '').trim();
|
|
215
|
+
if (!trimmed) {
|
|
216
|
+
throw new types_1.JiraConfigError('An issue key is required (e.g., "PROJ-123").');
|
|
217
|
+
}
|
|
218
|
+
await this.write(`/rest/api/2/issue/${encodeURIComponent(trimmed)}/comment`, { body });
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Shared, credentialed GET against the configured host. Delegates to
|
|
222
|
+
* `send()` for the security guards, then parses JSON. Returns the parsed
|
|
223
|
+
* JSON body on success; a non-JSON body is a `JiraConfigError`.
|
|
224
|
+
*/
|
|
225
|
+
async request(path) {
|
|
226
|
+
const res = await this.send(path, 'GET');
|
|
227
|
+
try {
|
|
228
|
+
return await res.json();
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
throw new types_1.JiraConfigError(`JIRA at ${this.host} returned a non-JSON response.`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Shared, credentialed write (POST/PUT) against the configured host
|
|
236
|
+
* (REQ-JIRA-007). Carries EVERY guard `request()` does via `send()` —
|
|
237
|
+
* `redirect: 'manual'`, host-lock, 401/403 → auth, 404 → not-found,
|
|
238
|
+
* non-2xx → config, credential-free messages. A JSON body is sent with
|
|
239
|
+
* `Content-Type: application/json`; a success with no/empty body (204, the
|
|
240
|
+
* common transition/assignee shape) resolves to `null` rather than erroring
|
|
241
|
+
* on the missing JSON.
|
|
242
|
+
*/
|
|
243
|
+
async write(path, body, method = 'POST') {
|
|
244
|
+
const res = await this.send(path, method, JSON.stringify(body ?? {}));
|
|
245
|
+
// 204 No Content (assignee/transition) has no body — don't force JSON.
|
|
246
|
+
if (res.status === 204)
|
|
247
|
+
return null;
|
|
248
|
+
try {
|
|
249
|
+
return await res.json();
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// A 2xx write with an empty/non-JSON body is still a success.
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* The single fetch + guard chokepoint shared by `request` and `write`. Every
|
|
258
|
+
* security guard lives here so no call path can skip one:
|
|
259
|
+
* - `redirect: 'manual'` — the `Authorization` header is never replayed
|
|
260
|
+
* across a redirect; any 3xx / opaqueredirect is refused, not followed.
|
|
261
|
+
* - only ever talks to the configured host.
|
|
262
|
+
* - 401 / 403 → `JiraAuthError`; 404 → `JiraNotFoundError`; network /
|
|
263
|
+
* non-2xx → `JiraConfigError`.
|
|
264
|
+
* - No thrown message ever contains the credential — only the host.
|
|
265
|
+
*
|
|
266
|
+
* Returns the raw `Response` on a 2xx; the caller parses the body.
|
|
267
|
+
*/
|
|
268
|
+
async send(path, method, jsonBody) {
|
|
269
|
+
const url = `${this.baseUrl}${path}`;
|
|
270
|
+
const headers = {
|
|
271
|
+
Authorization: this.authHeader,
|
|
272
|
+
Accept: 'application/json',
|
|
273
|
+
};
|
|
274
|
+
if (jsonBody !== undefined) {
|
|
275
|
+
headers['Content-Type'] = 'application/json';
|
|
276
|
+
}
|
|
277
|
+
let res;
|
|
278
|
+
try {
|
|
279
|
+
res = await fetch(url, {
|
|
280
|
+
method,
|
|
281
|
+
redirect: 'manual',
|
|
282
|
+
headers,
|
|
283
|
+
...(jsonBody !== undefined ? { body: jsonBody } : {}),
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
// Network / DNS — the message is fetch's own (no credential in it),
|
|
288
|
+
// but we prepend the host, never the header.
|
|
289
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
290
|
+
throw new types_1.JiraConfigError(`Could not reach JIRA at ${this.host}: ${msg}`);
|
|
291
|
+
}
|
|
292
|
+
// Refuse any redirect — do not follow it to another host.
|
|
293
|
+
if (res.type === 'opaqueredirect' ||
|
|
294
|
+
(res.status >= 300 && res.status < 400)) {
|
|
295
|
+
throw new types_1.JiraConfigError(`JIRA at ${this.host} returned an unexpected redirect; refusing to ` +
|
|
296
|
+
`follow it to another host.`);
|
|
297
|
+
}
|
|
298
|
+
if (res.status === 401 || res.status === 403) {
|
|
299
|
+
throw new types_1.JiraAuthError(`JIRA rejected the credentials (HTTP ${res.status}). ` +
|
|
300
|
+
`Check the email/token or PAT.`);
|
|
301
|
+
}
|
|
302
|
+
// A missing issue — or one the token can't see — is a distinct, expected
|
|
303
|
+
// signal (REQ-JIRA-003 A2). Must precede the generic !res.ok branch below,
|
|
304
|
+
// and stays credential-free like every other message here.
|
|
305
|
+
if (res.status === 404) {
|
|
306
|
+
throw new types_1.JiraNotFoundError(`JIRA at ${this.host} has no such issue, or you don't have access ` +
|
|
307
|
+
`to it (HTTP 404).`);
|
|
308
|
+
}
|
|
309
|
+
if (!res.ok) {
|
|
310
|
+
throw new types_1.JiraConfigError(`JIRA at ${this.host} returned HTTP ${res.status}.`);
|
|
311
|
+
}
|
|
312
|
+
return res;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
exports.JiraClient = JiraClient;
|
|
316
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/jira/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA config load / resolve / save (REQ-JIRA-001, A2).
|
|
3
|
+
*
|
|
4
|
+
* Config lives at `~/.specship/jira.json` — **user-level only, never in
|
|
5
|
+
* the project tree** (not even a gitignored project file), because it
|
|
6
|
+
* holds a secret. The path is overridable via `SPECSHIP_JIRA_CONFIG`
|
|
7
|
+
* (mirrors `SPECSHIP_SERVER_CONFIG`) so tests never touch the real home.
|
|
8
|
+
*/
|
|
9
|
+
import { JiraConfig, JiraCredentials, JiraDeployment } from './types';
|
|
10
|
+
/**
|
|
11
|
+
* Absolute path to the user-level JIRA config. Overridable via
|
|
12
|
+
* `SPECSHIP_JIRA_CONFIG` for tests. Never project-scoped.
|
|
13
|
+
*/
|
|
14
|
+
export declare function jiraConfigPath(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Read `~/.specship/jira.json`. Returns `null` when the file is absent.
|
|
17
|
+
* Throws `JiraConfigError` when it exists but is malformed — a corrupt
|
|
18
|
+
* secret file is a hard error, not a silent fall-through to `{}`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadJiraConfig(): JiraConfig | null;
|
|
21
|
+
/**
|
|
22
|
+
* Infer the deployment kind from which credentials are present. An
|
|
23
|
+
* explicit `deployment` field always wins. Otherwise: a PAT (with no
|
|
24
|
+
* email+token pair) ⇒ Data Center; an email+token pair ⇒ Cloud.
|
|
25
|
+
*/
|
|
26
|
+
export declare function inferDeployment(creds: {
|
|
27
|
+
deployment?: JiraDeployment;
|
|
28
|
+
email?: string;
|
|
29
|
+
apiToken?: string;
|
|
30
|
+
pat?: string;
|
|
31
|
+
}): JiraDeployment;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve effective credentials from environment (highest precedence,
|
|
34
|
+
* per-field) then the on-disk config. Throws `JiraConfigError` with an
|
|
35
|
+
* actionable message when required fields are missing or malformed.
|
|
36
|
+
*
|
|
37
|
+
* SECURITY: the thrown messages never include a token or PAT value.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveJiraCredentials(): JiraCredentials;
|
|
40
|
+
/**
|
|
41
|
+
* Persist config to `~/.specship/jira.json` with `0600` permissions —
|
|
42
|
+
* it holds a secret, so it must not be group/world readable.
|
|
43
|
+
*/
|
|
44
|
+
export declare function saveJiraConfig(config: JiraConfig): void;
|
|
45
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/jira/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EACL,UAAU,EACV,eAAe,EAEf,cAAc,EACf,MAAM,SAAS,CAAC;AAEjB;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAKvC;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,UAAU,GAAG,IAAI,CAsBlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,cAAc,CAWjB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,eAAe,CAyDxD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAQvD"}
|