@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.
Files changed (87) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +141 -0
  4. package/dist/IssueService.d.ts +144 -0
  5. package/dist/IssueService.d.ts.map +1 -0
  6. package/dist/IssueService.js +250 -0
  7. package/dist/IssueService.js.map +1 -0
  8. package/dist/JiraAuth.d.ts +84 -0
  9. package/dist/JiraAuth.d.ts.map +1 -0
  10. package/dist/JiraAuth.js +246 -0
  11. package/dist/JiraAuth.js.map +1 -0
  12. package/dist/JiraCliError.d.ts +42 -0
  13. package/dist/JiraCliError.d.ts.map +1 -0
  14. package/dist/JiraCliError.js +35 -0
  15. package/dist/JiraCliError.js.map +1 -0
  16. package/dist/MarkdownWriter.d.ts +56 -0
  17. package/dist/MarkdownWriter.d.ts.map +1 -0
  18. package/dist/MarkdownWriter.js +66 -0
  19. package/dist/MarkdownWriter.js.map +1 -0
  20. package/dist/bin.d.ts +3 -0
  21. package/dist/bin.d.ts.map +1 -0
  22. package/dist/bin.js +39 -0
  23. package/dist/bin.js.map +1 -0
  24. package/dist/commands/auth.d.ts +22 -0
  25. package/dist/commands/auth.d.ts.map +1 -0
  26. package/dist/commands/auth.js +89 -0
  27. package/dist/commands/auth.js.map +1 -0
  28. package/dist/commands/errorHandler.d.ts +13 -0
  29. package/dist/commands/errorHandler.d.ts.map +1 -0
  30. package/dist/commands/errorHandler.js +13 -0
  31. package/dist/commands/errorHandler.js.map +1 -0
  32. package/dist/commands/get.d.ts +13 -0
  33. package/dist/commands/get.d.ts.map +1 -0
  34. package/dist/commands/get.js +25 -0
  35. package/dist/commands/get.js.map +1 -0
  36. package/dist/commands/index.d.ts +11 -0
  37. package/dist/commands/index.d.ts.map +1 -0
  38. package/dist/commands/index.js +11 -0
  39. package/dist/commands/index.js.map +1 -0
  40. package/dist/commands/layers.d.ts +44 -0
  41. package/dist/commands/layers.d.ts.map +1 -0
  42. package/dist/commands/layers.js +100 -0
  43. package/dist/commands/layers.js.map +1 -0
  44. package/dist/commands/search.d.ts +18 -0
  45. package/dist/commands/search.d.ts.map +1 -0
  46. package/dist/commands/search.js +64 -0
  47. package/dist/commands/search.js.map +1 -0
  48. package/dist/index.d.ts +10 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +10 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/internal/NodeLayers.d.ts +7 -0
  53. package/dist/internal/NodeLayers.d.ts.map +1 -0
  54. package/dist/internal/NodeLayers.js +15 -0
  55. package/dist/internal/NodeLayers.js.map +1 -0
  56. package/dist/internal/frontmatter.d.ts +60 -0
  57. package/dist/internal/frontmatter.d.ts.map +1 -0
  58. package/dist/internal/frontmatter.js +130 -0
  59. package/dist/internal/frontmatter.js.map +1 -0
  60. package/dist/internal/jqlBuilder.d.ts +39 -0
  61. package/dist/internal/jqlBuilder.d.ts.map +1 -0
  62. package/dist/internal/jqlBuilder.js +47 -0
  63. package/dist/internal/jqlBuilder.js.map +1 -0
  64. package/dist/internal/oauthServer.d.ts +55 -0
  65. package/dist/internal/oauthServer.d.ts.map +1 -0
  66. package/dist/internal/oauthServer.js +113 -0
  67. package/dist/internal/oauthServer.js.map +1 -0
  68. package/package.json +86 -0
  69. package/src/IssueService.ts +378 -0
  70. package/src/JiraAuth.ts +476 -0
  71. package/src/JiraCliError.ts +44 -0
  72. package/src/MarkdownWriter.ts +112 -0
  73. package/src/bin.ts +62 -0
  74. package/src/commands/auth.ts +124 -0
  75. package/src/commands/errorHandler.ts +14 -0
  76. package/src/commands/get.ts +42 -0
  77. package/src/commands/index.ts +11 -0
  78. package/src/commands/layers.ts +142 -0
  79. package/src/commands/search.ts +102 -0
  80. package/src/index.ts +26 -0
  81. package/src/internal/NodeLayers.ts +17 -0
  82. package/src/internal/frontmatter.ts +170 -0
  83. package/src/internal/jqlBuilder.ts +49 -0
  84. package/src/internal/oauthServer.ts +203 -0
  85. package/test/jqlBuilder.test.ts +45 -0
  86. package/tsconfig.json +32 -0
  87. package/vitest.config.ts +12 -0
@@ -0,0 +1,130 @@
1
+ /**
2
+ * YAML frontmatter serialization for Jira issues using `gray-matter`.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - {@link serializeIssue} produces `---\nyaml\n---\nmarkdown` for a single issue.
7
+ * - {@link buildCombinedMarkdown} concatenates all issues with a JQL header for single-file export.
8
+ *
9
+ * @internal
10
+ */
11
+ import matter from "gray-matter";
12
+ /**
13
+ * Extract front-matter data from an issue.
14
+ *
15
+ * @param issue - The issue to extract from
16
+ * @returns Front-matter object
17
+ *
18
+ * @category Utilities
19
+ */
20
+ export const extractFrontMatter = (issue) => ({
21
+ key: issue.key,
22
+ id: issue.id,
23
+ summary: issue.summary,
24
+ status: issue.status,
25
+ type: issue.type,
26
+ priority: issue.priority,
27
+ assignee: issue.assignee,
28
+ reporter: issue.reporter,
29
+ created: issue.created.toISOString(),
30
+ updated: issue.updated.toISOString(),
31
+ fixVersions: issue.fixVersions,
32
+ labels: issue.labels,
33
+ components: issue.components,
34
+ url: issue.url
35
+ });
36
+ /**
37
+ * Serialize an issue to markdown with front-matter.
38
+ *
39
+ * @param issue - The issue to serialize
40
+ * @returns Markdown string with YAML front-matter
41
+ *
42
+ * @category Serialization
43
+ */
44
+ export const serializeIssue = (issue) => {
45
+ const frontMatter = extractFrontMatter(issue);
46
+ const content = buildMarkdownContent(issue);
47
+ return matter.stringify(content, frontMatter);
48
+ };
49
+ /**
50
+ * Build markdown content for an issue (without front-matter).
51
+ *
52
+ * @param issue - The issue to build content for
53
+ * @returns Markdown content string
54
+ *
55
+ * @category Serialization
56
+ */
57
+ export const buildMarkdownContent = (issue) => {
58
+ const parts = [];
59
+ // Title
60
+ parts.push(`# ${issue.key}: ${issue.summary}`);
61
+ parts.push("");
62
+ // Description
63
+ if (issue.description) {
64
+ parts.push("## Description");
65
+ parts.push("");
66
+ parts.push(issue.description);
67
+ parts.push("");
68
+ }
69
+ // Attachments
70
+ if (issue.attachments.length > 0) {
71
+ parts.push("## Attachments");
72
+ parts.push("");
73
+ for (const attachment of issue.attachments) {
74
+ parts.push(`- [${attachment.filename}](${attachment.url})`);
75
+ }
76
+ parts.push("");
77
+ }
78
+ // Comments
79
+ if (issue.comments.length > 0) {
80
+ parts.push("## Comments");
81
+ parts.push("");
82
+ for (const comment of issue.comments) {
83
+ const date = comment.created.toISOString().split("T")[0];
84
+ parts.push(`### ${comment.author} (${date})`);
85
+ parts.push("");
86
+ parts.push(comment.body);
87
+ parts.push("");
88
+ }
89
+ }
90
+ return parts.join("\n");
91
+ };
92
+ /**
93
+ * Build a combined markdown file for multiple issues.
94
+ *
95
+ * @param issues - The issues to include
96
+ * @param jql - The JQL query used (for header)
97
+ * @returns Combined markdown string
98
+ *
99
+ * @category Serialization
100
+ */
101
+ export const buildCombinedMarkdown = (issues, jql) => {
102
+ const parts = [];
103
+ // Header
104
+ parts.push("# Jira Export");
105
+ parts.push("");
106
+ parts.push(`Query: \`${jql}\``);
107
+ parts.push(`Exported: ${new Date().toISOString()}`);
108
+ parts.push(`Total: ${issues.length} tickets`);
109
+ parts.push("");
110
+ parts.push("---");
111
+ parts.push("");
112
+ // Issues
113
+ for (const issue of issues) {
114
+ parts.push(`## ${issue.key}: ${issue.summary}`);
115
+ parts.push("");
116
+ parts.push(`**Status:** ${issue.status} | **Type:** ${issue.type}${issue.priority ? ` | **Priority:** ${issue.priority}` : ""}`);
117
+ if (issue.assignee) {
118
+ parts.push(`**Assignee:** ${issue.assignee}`);
119
+ }
120
+ parts.push("");
121
+ if (issue.description) {
122
+ parts.push(issue.description);
123
+ parts.push("");
124
+ }
125
+ parts.push("---");
126
+ parts.push("");
127
+ }
128
+ return parts.join("\n");
129
+ };
130
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/internal/frontmatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * JQL query construction with proper value escaping.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - {@link buildByVersionJql} generates `fixVersion = "X"` queries with optional project filter.
7
+ * - {@link escapeJqlValue} handles backslashes, quotes, newlines, carriage returns.
8
+ *
9
+ * @internal
10
+ */
11
+ /**
12
+ * Build JQL query to find issues by fix version.
13
+ *
14
+ * @param version - The fix version to search for
15
+ * @param project - Optional project key to filter by
16
+ * @returns JQL query string
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * buildByVersionJql("1.0.0")
21
+ * // => 'fixVersion = "1.0.0" ORDER BY key ASC'
22
+ *
23
+ * buildByVersionJql("1.0.0", "PROJ")
24
+ * // => 'project = "PROJ" AND fixVersion = "1.0.0" ORDER BY key ASC'
25
+ * ```
26
+ *
27
+ * @category JQL Builders
28
+ */
29
+ export declare const buildByVersionJql: (version: string, project?: string) => string;
30
+ /**
31
+ * Escape a value for use in JQL queries.
32
+ *
33
+ * @param value - The value to escape
34
+ * @returns Escaped value safe for JQL
35
+ *
36
+ * @category Utilities
37
+ */
38
+ export declare const escapeJqlValue: (value: string) => string;
39
+ //# sourceMappingURL=jqlBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jqlBuilder.d.ts","sourceRoot":"","sources":["../../src/internal/jqlBuilder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,MAIrE,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,KAAG,MAKrB,CAAA"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * JQL query construction with proper value escaping.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - {@link buildByVersionJql} generates `fixVersion = "X"` queries with optional project filter.
7
+ * - {@link escapeJqlValue} handles backslashes, quotes, newlines, carriage returns.
8
+ *
9
+ * @internal
10
+ */
11
+ /**
12
+ * Build JQL query to find issues by fix version.
13
+ *
14
+ * @param version - The fix version to search for
15
+ * @param project - Optional project key to filter by
16
+ * @returns JQL query string
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * buildByVersionJql("1.0.0")
21
+ * // => 'fixVersion = "1.0.0" ORDER BY key ASC'
22
+ *
23
+ * buildByVersionJql("1.0.0", "PROJ")
24
+ * // => 'project = "PROJ" AND fixVersion = "1.0.0" ORDER BY key ASC'
25
+ * ```
26
+ *
27
+ * @category JQL Builders
28
+ */
29
+ export const buildByVersionJql = (version, project) => {
30
+ const escapedVersion = escapeJqlValue(version);
31
+ const projectClause = project !== undefined ? `project = "${escapeJqlValue(project)}" AND ` : "";
32
+ return `${projectClause}fixVersion = "${escapedVersion}" ORDER BY key ASC`;
33
+ };
34
+ /**
35
+ * Escape a value for use in JQL queries.
36
+ *
37
+ * @param value - The value to escape
38
+ * @returns Escaped value safe for JQL
39
+ *
40
+ * @category Utilities
41
+ */
42
+ export const escapeJqlValue = (value) => value
43
+ .replace(/\\/g, "\\\\")
44
+ .replace(/"/g, "\\\"")
45
+ .replace(/\n/g, "\\n")
46
+ .replace(/\r/g, "\\r");
47
+ //# sourceMappingURL=jqlBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jqlBuilder.js","sourceRoot":"","sources":["../../src/internal/jqlBuilder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAe,EAAE,OAAgB,EAAU,EAAE;IAC7E,MAAM,cAAc,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;IAC9C,MAAM,aAAa,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IAChG,OAAO,GAAG,aAAa,iBAAiB,cAAc,oBAAoB,CAAA;AAC5E,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAa,EAAU,EAAE,CACtD,KAAK;KACF,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;KACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;KACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;KACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA"}
@@ -0,0 +1,55 @@
1
+ import * as HttpServer from "@effect/platform/HttpServer";
2
+ import type { ServeError } from "@effect/platform/HttpServerError";
3
+ import { OAuthError } from "@knpkv/atlassian-common/auth";
4
+ import * as Context from "effect/Context";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Layer from "effect/Layer";
7
+ /**
8
+ * Factory service for creating HTTP servers.
9
+ * This allows mocking the server creation in tests.
10
+ *
11
+ * @category Services
12
+ */
13
+ export interface HttpServerFactory {
14
+ readonly createServerLayer: (port: number) => Layer.Layer<HttpServer.HttpServer, ServeError, never>;
15
+ }
16
+ declare const HttpServerFactoryTag_base: Context.TagClass<HttpServerFactoryTag, "@knpkv/jira-cli/HttpServerFactory", HttpServerFactory>;
17
+ /**
18
+ * Tag for the HttpServerFactory service.
19
+ *
20
+ * @category Services
21
+ */
22
+ export declare class HttpServerFactoryTag extends HttpServerFactoryTag_base {
23
+ }
24
+ /**
25
+ * Create a HttpServerFactory layer from a layer factory function.
26
+ * This allows injecting platform-specific implementations.
27
+ *
28
+ * @param createLayerFn - Function that creates HttpServer layer for a given port
29
+ * @returns Layer providing HttpServerFactory
30
+ *
31
+ * @category Layers
32
+ */
33
+ export declare const makeHttpServerFactory: (createLayerFn: (port: number) => Layer.Layer<HttpServer.HttpServer, ServeError, never>) => Layer.Layer<HttpServerFactoryTag>;
34
+ /**
35
+ * Result from the OAuth callback server.
36
+ */
37
+ export interface CallbackServerResult {
38
+ /** Promise that resolves with the authorization code */
39
+ readonly codePromise: Effect.Effect<string, OAuthError>;
40
+ /** Shutdown the callback server */
41
+ readonly shutdown: Effect.Effect<void, never>;
42
+ /** The port the server is listening on */
43
+ readonly port: number;
44
+ }
45
+ /**
46
+ * Start a local HTTP server to receive OAuth callback.
47
+ *
48
+ * @param expectedState - The state parameter to verify against CSRF
49
+ * @returns Server control interface with code promise, shutdown, and port
50
+ *
51
+ * @category OAuth
52
+ */
53
+ export declare const startCallbackServer: (expectedState: string) => Effect.Effect<CallbackServerResult, OAuthError, HttpServerFactoryTag>;
54
+ export {};
55
+ //# sourceMappingURL=oauthServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthServer.d.ts","sourceRoot":"","sources":["../../src/internal/oauthServer.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,UAAU,MAAM,6BAA6B,CAAA;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAA;AAGlE,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAKrC;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC,KAAK,CACvD,UAAU,CAAC,UAAU,EACrB,UAAU,EACV,KAAK,CACN,CAAA;CACF;;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,yBAGvC;CAAG;AAEN;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GAChC,eAAe,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,CAAC,KACrF,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAG/B,CAAA;AAwCJ;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,wDAAwD;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACvD,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC7C,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC9B,eAAe,MAAM,KACpB,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,UAAU,EAAE,oBAAoB,CA2EnE,CAAA"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Local HTTP callback server for OAuth2 authorization code capture.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Deferred-coordinated lifecycle**: {@link startCallbackServer} returns a `codePromise`
7
+ * (Deferred) and a `shutdown` effect. The server validates the CSRF `state` parameter
8
+ * and resolves the Deferred with the authorization code.
9
+ * - **Port auto-discovery**: Tries default port 8585, increments on conflict.
10
+ *
11
+ * @internal
12
+ */
13
+ import * as HttpRouter from "@effect/platform/HttpRouter";
14
+ import * as HttpServer from "@effect/platform/HttpServer";
15
+ import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
16
+ import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
17
+ import { OAuthError } from "@knpkv/atlassian-common/auth";
18
+ import * as Context from "effect/Context";
19
+ import * as Deferred from "effect/Deferred";
20
+ import * as Effect from "effect/Effect";
21
+ import * as Fiber from "effect/Fiber";
22
+ import * as Layer from "effect/Layer";
23
+ const DEFAULT_PORT = 8585;
24
+ const MAX_PORT_ATTEMPTS = 10;
25
+ /**
26
+ * Tag for the HttpServerFactory service.
27
+ *
28
+ * @category Services
29
+ */
30
+ export class HttpServerFactoryTag extends Context.Tag("@knpkv/jira-cli/HttpServerFactory")() {
31
+ }
32
+ /**
33
+ * Create a HttpServerFactory layer from a layer factory function.
34
+ * This allows injecting platform-specific implementations.
35
+ *
36
+ * @param createLayerFn - Function that creates HttpServer layer for a given port
37
+ * @returns Layer providing HttpServerFactory
38
+ *
39
+ * @category Layers
40
+ */
41
+ export const makeHttpServerFactory = (createLayerFn) => Layer.succeed(HttpServerFactoryTag, {
42
+ createServerLayer: createLayerFn
43
+ });
44
+ /**
45
+ * Check if a port is available by attempting to start a server.
46
+ */
47
+ const isPortAvailable = (port) => Effect.gen(function* () {
48
+ const factory = yield* HttpServerFactoryTag;
49
+ const serverLayer = factory.createServerLayer(port);
50
+ const result = yield* Layer.build(serverLayer).pipe(Effect.scoped, Effect.as(true), Effect.catchAll(() => Effect.succeed(false)));
51
+ return result;
52
+ });
53
+ /**
54
+ * Find an available port starting from the default.
55
+ */
56
+ const findAvailablePort = () => Effect.gen(function* () {
57
+ for (let attempt = 0; attempt < MAX_PORT_ATTEMPTS; attempt++) {
58
+ const port = DEFAULT_PORT + attempt;
59
+ const available = yield* isPortAvailable(port);
60
+ if (available) {
61
+ return port;
62
+ }
63
+ }
64
+ return yield* Effect.fail(new OAuthError({
65
+ step: "authorize",
66
+ cause: `Could not find available port (tried ${DEFAULT_PORT}-${DEFAULT_PORT + MAX_PORT_ATTEMPTS - 1}). Close other applications using these ports.`
67
+ }));
68
+ });
69
+ /**
70
+ * Start a local HTTP server to receive OAuth callback.
71
+ *
72
+ * @param expectedState - The state parameter to verify against CSRF
73
+ * @returns Server control interface with code promise, shutdown, and port
74
+ *
75
+ * @category OAuth
76
+ */
77
+ export const startCallbackServer = (expectedState) => Effect.gen(function* () {
78
+ const factory = yield* HttpServerFactoryTag;
79
+ const port = yield* findAvailablePort();
80
+ const deferred = yield* Deferred.make();
81
+ const readyDeferred = yield* Deferred.make();
82
+ const app = HttpRouter.empty.pipe(HttpRouter.get("/callback", Effect.gen(function* () {
83
+ const req = yield* HttpServerRequest.HttpServerRequest;
84
+ const url = new URL(req.url, `http://localhost:${port}`);
85
+ const code = url.searchParams.get("code");
86
+ const state = url.searchParams.get("state");
87
+ const error = url.searchParams.get("error");
88
+ const errorDescription = url.searchParams.get("error_description");
89
+ if (error) {
90
+ yield* Deferred.fail(deferred, new OAuthError({ step: "authorize", cause: errorDescription ?? error }));
91
+ return HttpServerResponse.html("<html><body><h1>Authorization Failed</h1><p>You can close this window.</p></body></html>");
92
+ }
93
+ if (state !== expectedState) {
94
+ yield* Deferred.fail(deferred, new OAuthError({ step: "authorize", cause: "State mismatch - possible CSRF attack" }));
95
+ return HttpServerResponse.html("<html><body><h1>Security Error</h1><p>State verification failed.</p></body></html>");
96
+ }
97
+ if (!code) {
98
+ yield* Deferred.fail(deferred, new OAuthError({ step: "authorize", cause: "No authorization code received" }));
99
+ return HttpServerResponse.html("<html><body><h1>Error</h1><p>No authorization code received.</p></body></html>");
100
+ }
101
+ yield* Deferred.succeed(deferred, code);
102
+ return HttpServerResponse.html("<html><body><h1>Success!</h1><p>You can close this window and return to the terminal.</p></body></html>");
103
+ })));
104
+ const serverLayer = factory.createServerLayer(port);
105
+ const serverFiber = yield* HttpServer.serve(app).pipe(Layer.provide(serverLayer), Layer.build, Effect.tap(() => Deferred.succeed(readyDeferred, undefined)), Effect.tapError((err) => Deferred.fail(readyDeferred, new OAuthError({ step: "authorize", cause: err }))), Effect.flatMap(() => Effect.never), Effect.scoped, Effect.fork);
106
+ yield* Deferred.await(readyDeferred);
107
+ return {
108
+ codePromise: Deferred.await(deferred),
109
+ shutdown: Fiber.interrupt(serverFiber).pipe(Effect.asVoid),
110
+ port
111
+ };
112
+ });
113
+ //# sourceMappingURL=oauthServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthServer.js","sourceRoot":"","sources":["../../src/internal/oauthServer.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@knpkv/jira-cli",
3
+ "version": "0.1.1",
4
+ "description": "CLI tool to fetch Jira tickets and export to markdown",
5
+ "license": "MIT",
6
+ "author": "knpkv",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/knpkv/npm.git",
10
+ "directory": "packages/jira-cli"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/knpkv/npm/issues"
14
+ },
15
+ "homepage": "https://github.com/knpkv/npm/tree/main/packages/jira-cli#readme",
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "bin": {
21
+ "jira": "./dist/bin.js"
22
+ },
23
+ "exports": {
24
+ ".": {
25
+ "import": "./dist/index.js",
26
+ "types": "./dist/index.d.ts"
27
+ },
28
+ "./JiraAuth": {
29
+ "import": "./dist/JiraAuth.js",
30
+ "types": "./dist/JiraAuth.d.ts"
31
+ },
32
+ "./IssueService": {
33
+ "import": "./dist/IssueService.js",
34
+ "types": "./dist/IssueService.d.ts"
35
+ },
36
+ "./MarkdownWriter": {
37
+ "import": "./dist/MarkdownWriter.js",
38
+ "types": "./dist/MarkdownWriter.d.ts"
39
+ },
40
+ "./JiraCliError": {
41
+ "import": "./dist/JiraCliError.js",
42
+ "types": "./dist/JiraCliError.d.ts"
43
+ }
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "peerDependencies": {
49
+ "@effect/platform": ">=0.93.0 <0.96.0",
50
+ "effect": ">=3.19.3"
51
+ },
52
+ "dependencies": {
53
+ "@effect/cli": "latest",
54
+ "@effect/platform-node": "latest",
55
+ "gray-matter": "^4.0.3",
56
+ "@knpkv/atlassian-common": "0.2.0",
57
+ "@knpkv/jira-api-client": "0.2.0"
58
+ },
59
+ "devDependencies": {
60
+ "@effect/platform": "latest",
61
+ "@effect/vitest": "latest",
62
+ "@types/node": "latest",
63
+ "effect": "latest",
64
+ "typescript": "~5.9.0",
65
+ "vitest": "^4.0.13"
66
+ },
67
+ "keywords": [
68
+ "effect",
69
+ "effect-ts",
70
+ "jira",
71
+ "markdown",
72
+ "cli",
73
+ "atlassian",
74
+ "tickets",
75
+ "export"
76
+ ],
77
+ "scripts": {
78
+ "build": "tsc",
79
+ "clean": "rimraf dist dist-test .tsbuildinfo",
80
+ "test": "vitest run",
81
+ "test:watch": "vitest",
82
+ "lint": "eslint src",
83
+ "lint:fix": "eslint src --fix",
84
+ "check": "tsc --noEmit"
85
+ }
86
+ }