@knpkv/jira-cli 1.4.1 → 1.5.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 CHANGED
@@ -1,5 +1,33 @@
1
1
  # @knpkv/jira-cli
2
2
 
3
+ ## 1.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#477](https://github.com/knpkv/npm/pull/477) [`b4a69a0`](https://github.com/knpkv/npm/commit/b4a69a056ce3e2575df70d7aaea1a202f49b6a56) Thanks [@konopkov](https://github.com/konopkov)! - `JiraAuthService` and `ConfluenceAuthService` are now derived from atlassian-common's `AtlassianCliAuth` instead of re-spelling it, and `LoginOptions` / `AccessibleSite` are re-exported from there. The types are unchanged: a type-equality check against the previous hand-written interfaces passed before the switch. `ConfluenceAuthService` keeps its one real difference, a string access token and no `getSiteUrl`.
8
+
9
+ - [#475](https://github.com/knpkv/npm/pull/475) [`08c62b7`](https://github.com/knpkv/npm/commit/08c62b787c649d8f78753515be6faf317791d62b) Thanks [@konopkov](https://github.com/konopkov)! - `auth use <profile>` and `auth remove <profile>` now fail with a typed `ProfileNotFoundError` (exported from `@knpkv/atlassian-common/cli-auth`) and exit non-zero when no stored profile matches. The failure names the profile (`Profile not found: <profile>`, on stderr through each CLI's error handler); previously the line went to stdout and the command exited 0, so a script could not tell a typo from a switch.
10
+ - Updated dependencies [[`08c62b7`](https://github.com/knpkv/npm/commit/08c62b787c649d8f78753515be6faf317791d62b)]:
11
+ - @knpkv/atlassian-common@1.8.0
12
+
13
+ ## 1.5.0
14
+
15
+ ### Minor Changes
16
+
17
+ - [#472](https://github.com/knpkv/npm/pull/472) [`d12c336`](https://github.com/knpkv/npm/commit/d12c3364e56abb7cfd32aa693c5406788a67e3f5) Thanks [@konopkov](https://github.com/konopkov)! - One shared `auth` command group for jira and confluence: `makeAuthCommand(service, descriptor, options)` in `@knpkv/atlassian-common/cli-auth`. Each CLI exports one descriptor (`jiraCliDescriptor`, `confluenceCliDescriptor`: command name, product name, login scopes) that feeds both its auth and its `auth` commands.
18
+
19
+ - `jira auth create` lists every scope `jira auth login` requests. It previously listed three of five (missing `write:jira-work` and `manage:jira-project`), so an app set up from its instructions could not log in.
20
+ - `jira auth manage` is new: it opens the developer console with the scope and callback checklist.
21
+ - `confluence auth profiles`, `auth use <profile>` and `auth remove <profile>` are new; the docs already described them.
22
+ - `confluence auth status` prints the active profile, account, site and profile ID.
23
+ - `auth create` and `auth manage` print the URL first and exit non-zero when no browser opens (jira's `create` used to ignore that).
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [[`d12c336`](https://github.com/knpkv/npm/commit/d12c3364e56abb7cfd32aa693c5406788a67e3f5)]:
28
+ - @knpkv/atlassian-common@1.7.0
29
+ - @knpkv/agent-skills@0.3.3
30
+
3
31
  ## 1.4.1
4
32
 
5
33
  ### Patch Changes
package/README.md CHANGED
@@ -22,7 +22,9 @@ cd packages/jira-cli && pnpm link --global
22
22
  jira auth create
23
23
  ```
24
24
 
25
- Opens Atlassian Developer Console. Create a new OAuth 2.0 (3LO) app with:
25
+ Prints the URL and opens the Atlassian Developer Console (the command fails if no browser
26
+ opens; finish from the printed URL). Create a new OAuth 2.0 (3LO) app with the scopes it
27
+ prints, which are exactly the ones `jira auth login` requests:
26
28
 
27
29
  **Permissions:**
28
30
 
@@ -252,6 +254,7 @@ set, including surplus copies of a URL that _is_ desired. Other categories are n
252
254
 
253
255
  ```bash
254
256
  jira auth create # Open Atlassian console to create OAuth app
257
+ jira auth manage # Open the console to edit the app's scopes
255
258
  jira auth configure # Set client ID and secret
256
259
  jira auth login # Authenticate via OAuth
257
260
  jira auth profiles # List stored accounts/sites
@@ -16,65 +16,18 @@
16
16
  *
17
17
  * @module
18
18
  */
19
- import type { OAuthError } from "@knpkv/atlassian-common/auth";
20
- import type { AuthProfile, FileSystemError, HomeDirectoryError, OAuthConfig, OAuthUser } from "@knpkv/atlassian-common/config";
19
+ import { type AtlassianCliAuth, type AtlassianCliDescriptor } from "@knpkv/atlassian-common/cli-auth";
21
20
  import * as Context from "effect/Context";
22
- import type * as Effect from "effect/Effect";
23
21
  import * as Layer from "effect/Layer";
24
- import type * as PlatformError from "effect/PlatformError";
25
- import type * as Redacted from "effect/Redacted";
26
22
  import type { AuthMissingError } from "./JiraCliError.js";
23
+ export type { AccessibleSite, LoginOptions } from "@knpkv/atlassian-common/cli-auth";
27
24
  /**
28
- * Options for the login method.
29
- *
30
- * @category Types
31
- */
32
- export interface LoginOptions {
33
- /** Site URL to select (for accounts with multiple sites) */
34
- readonly siteUrl?: string;
35
- }
36
- /**
37
- * Information about an accessible Jira site.
38
- *
39
- * @category Types
40
- */
41
- export interface AccessibleSite {
42
- readonly id: string;
43
- readonly name: string;
44
- readonly url: string;
45
- }
46
- /**
47
- * JiraAuth service interface.
25
+ * JiraAuth service interface: the shared Atlassian CLI auth, failing with Jira's own
26
+ * {@link AuthMissingError} when nobody is logged in.
48
27
  *
49
28
  * @category Services
50
29
  */
51
- export interface JiraAuthService {
52
- /** Configure OAuth client credentials */
53
- readonly configure: (config: OAuthConfig) => Effect.Effect<void, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
54
- /** Check if OAuth is configured */
55
- readonly isConfigured: () => Effect.Effect<boolean, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
56
- /** Start OAuth login flow. Returns list of sites if multiple are available. */
57
- readonly login: (options?: LoginOptions) => Effect.Effect<ReadonlyArray<AccessibleSite> | void, OAuthError | FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
58
- /** Remove stored authentication */
59
- readonly logout: () => Effect.Effect<void, OAuthError | FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
60
- /** Get access token, refreshing if needed */
61
- readonly getAccessToken: () => Effect.Effect<Redacted.Redacted<string>, AuthMissingError | OAuthError | FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
62
- /** Get cloud ID from stored token */
63
- readonly getCloudId: () => Effect.Effect<string, AuthMissingError | FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
64
- /** Get site URL from stored token */
65
- readonly getSiteUrl: () => Effect.Effect<string, AuthMissingError | FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
66
- /** Get current user info from stored token */
67
- readonly getCurrentUser: () => Effect.Effect<OAuthUser | null, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
68
- /** Get active auth profile */
69
- readonly getActiveProfile: () => Effect.Effect<AuthProfile | null, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
70
- /** List stored auth profiles */
71
- readonly listProfiles: () => Effect.Effect<ReadonlyArray<AuthProfile>, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
72
- /** Switch active profile by ID, name, site URL, cloud ID, or account ID */
73
- readonly switchProfile: (selector: string) => Effect.Effect<AuthProfile | null, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
74
- /** Remove stored profile by ID, name, site URL, cloud ID, or account ID */
75
- readonly removeProfile: (selector: string) => Effect.Effect<AuthProfile | null, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
76
- /** Check if user is logged in */
77
- readonly isLoggedIn: () => Effect.Effect<boolean, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>;
30
+ export interface JiraAuthService extends AtlassianCliAuth<AuthMissingError> {
78
31
  }
79
32
  declare const JiraAuth_base: Context.ServiceClass<JiraAuth, "@knpkv/jira-cli/JiraAuth", JiraAuthService>;
80
33
  /**
@@ -98,11 +51,12 @@ declare const JiraAuth_base: Context.ServiceClass<JiraAuth, "@knpkv/jira-cli/Jir
98
51
  */
99
52
  export declare class JiraAuth extends JiraAuth_base {
100
53
  }
54
+ /** Who the Jira CLI is: shared by its auth and its `auth` commands, so both name the same scopes. */
55
+ export declare const jiraCliDescriptor: AtlassianCliDescriptor;
101
56
  /**
102
57
  * Layer for JiraAuth service.
103
58
  *
104
59
  * @category Layers
105
60
  */
106
61
  export declare const layer: Layer.Layer<JiraAuth, never, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/Crypto").Crypto | import("effect/http/HttpClient").HttpClient>;
107
- export {};
108
62
  //# sourceMappingURL=JiraAuth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"JiraAuth.d.ts","sourceRoot":"","sources":["../src/JiraAuth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"JiraAuth.d.ts","sourceRoot":"","sources":["../src/JiraAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAG5B,MAAM,kCAAkC,CAAA;AACzC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAqBzD,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEpF;;;;;GAKG;AACH,MAAM,WAAW,eAAgB,SAAQ,gBAAgB,CAAC,gBAAgB,CAAC;CAAG;;AAE9E;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,QAAS,SAAQ,aAGC;CAAG;AAElC,qGAAqG;AACrG,eAAO,MAAM,iBAAiB,EAAE,sBAI/B,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,KAAK,+KAOoB,CAAA"}
package/dist/JiraAuth.js CHANGED
@@ -1,3 +1,21 @@
1
+ /**
2
+ * OAuth2 authentication service for Jira CLI with token refresh and concurrency locking.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Service pattern**: {@link JiraAuth} is a `Context.Tag` whose layer requires `HttpClient`,
7
+ * `ChildProcessSpawner` and `Crypto`. The flow — refresh lock, rotating-token persistence,
8
+ * browser login, profiles — is `@knpkv/atlassian-common/cli-auth`'s, bound to the
9
+ * `"jira-cli"` storage namespace, Jira's scopes and Jira's names.
10
+ *
11
+ * **Common tasks**
12
+ *
13
+ * - Get a valid access token: `auth.getAccessToken()` (auto-refreshes if expired)
14
+ * - Full login flow: `auth.login()`
15
+ * - Check auth state: `auth.isLoggedIn()`
16
+ *
17
+ * @module
18
+ */
1
19
  import { makeAtlassianCliAuth, NodeCliAuthLive } from "@knpkv/atlassian-common/cli-auth";
2
20
  import * as Context from "effect/Context";
3
21
  import * as Layer from "effect/Layer";
@@ -40,16 +58,20 @@ const JIRA_CLI_SCOPES = [
40
58
  */
41
59
  export class JiraAuth extends Context.Service()("@knpkv/jira-cli/JiraAuth") {
42
60
  }
61
+ /** Who the Jira CLI is: shared by its auth and its `auth` commands, so both name the same scopes. */
62
+ export const jiraCliDescriptor = {
63
+ commandName: "jira",
64
+ productName: "Jira",
65
+ scopes: JIRA_CLI_SCOPES
66
+ };
43
67
  /**
44
68
  * Layer for JiraAuth service.
45
69
  *
46
70
  * @category Layers
47
71
  */
48
72
  export const layer = Layer.effect(JiraAuth, makeAtlassianCliAuth({
73
+ ...jiraCliDescriptor,
49
74
  toolName: "jira-cli",
50
- commandName: "jira",
51
- productName: "Jira",
52
- scopes: JIRA_CLI_SCOPES,
53
75
  authMissing
54
76
  })).pipe(Layer.provide(NodeCliAuthLive));
55
77
  //# sourceMappingURL=JiraAuth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"JiraAuth.js","sourceRoot":"","sources":["../src/JiraAuth.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AAQxF,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAIrC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,+GAA+G;AAC/G,MAAM,eAAe,GAAG;IACtB,0CAA0C;IAC1C,gBAAgB;IAChB,kCAAkC;IAClC,iBAAiB;IACjB,oEAAoE;IACpE,sEAAsE;IACtE,iEAAiE;IACjE,qBAAqB;IACrB,0EAA0E;IAC1E,gBAAgB;IAChB,oEAAoE;IACpE,SAAS;IACT,gEAAgE;IAChE,gBAAgB;CACjB,CAAA;AA4FD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,QAAS,SAAQ,OAAO,CAAC,OAAO,EAG1C,CAAC,0BAA0B,CAAC;CAAG;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAC/B,QAAQ,EACR,oBAAoB,CAAC;IACnB,QAAQ,EAAE,UAAU;IACpB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,MAAM,EAAE,eAAe;IACvB,WAAW;CACZ,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"JiraAuth.js","sourceRoot":"","sources":["../src/JiraAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAGL,oBAAoB,EACpB,eAAe,EAChB,MAAM,kCAAkC,CAAA;AACzC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,+GAA+G;AAC/G,MAAM,eAAe,GAAG;IACtB,0CAA0C;IAC1C,gBAAgB;IAChB,kCAAkC;IAClC,iBAAiB;IACjB,oEAAoE;IACpE,sEAAsE;IACtE,iEAAiE;IACjE,qBAAqB;IACrB,0EAA0E;IAC1E,gBAAgB;IAChB,oEAAoE;IACpE,SAAS;IACT,gEAAgE;IAChE,gBAAgB;CACjB,CAAA;AAYD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,QAAS,SAAQ,OAAO,CAAC,OAAO,EAG1C,CAAC,0BAA0B,CAAC;CAAG;AAElC,qGAAqG;AACrG,MAAM,CAAC,MAAM,iBAAiB,GAA2B;IACvD,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,MAAM,EAAE,eAAe;CACxB,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAC/B,QAAQ,EACR,oBAAoB,CAAC;IACnB,GAAG,iBAAiB;IACpB,QAAQ,EAAE,UAAU;IACpB,WAAW;CACZ,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAA"}
@@ -1,4 +1,3 @@
1
- import { Command, Prompt } from "effect/cli";
2
1
  import { JiraAuth } from "../JiraAuth.js";
3
- export declare const authCommand: Command.Command<"auth", {}, {}, import("@knpkv/atlassian-common/config").FileSystemError | import("@knpkv/atlassian-common/config").HomeDirectoryError | import("@knpkv/atlassian-common/auth").OAuthError | import("effect/PlatformError").PlatformError | import("effect/Terminal").QuitError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | JiraAuth | Prompt.Environment>;
2
+ export declare const authCommand: import("effect/cli/Command").Command<"auth", {}, {}, import("@knpkv/atlassian-common/cli-auth").BrowserOpenError | import("@knpkv/atlassian-common").FileSystemError | import("@knpkv/atlassian-common").HomeDirectoryError | import("@knpkv/atlassian-common").OAuthError | import("effect/PlatformError").PlatformError | import("@knpkv/atlassian-common/cli-auth").ProfileNotFoundError | import("effect/Terminal").QuitError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | JiraAuth | import("effect/cli/Prompt").Environment>;
4
3
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAWA,OAAO,EAAoB,OAAO,EAAmB,MAAM,EAAE,MAAM,YAAY,CAAA;AAI/E,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAgJzC,eAAO,MAAM,WAAW,oYAYvB,CAAA"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAqB,MAAM,gBAAgB,CAAA;AAE5D,eAAO,MAAM,WAAW,2hBAA2E,CAAA"}
@@ -1,133 +1,9 @@
1
1
  /**
2
- * Auth subcommands: create, configure, login, logout, status.
3
- *
4
- * **Mental model**
5
- *
6
- * - Each command is a standalone `Command.make` that yields into `JiraAuth` service methods.
7
- * - `create` opens the Atlassian Developer Console; `configure` prompts for client ID/secret.
2
+ * `jira auth`: the shared Atlassian auth commands over {@link JiraAuth}.
8
3
  *
9
4
  * @internal
10
5
  */
11
- import { openBrowser } from "@knpkv/atlassian-common/cli-auth";
12
- import { Argument as Args, Command, Flag as Options, Prompt } from "effect/cli";
13
- import * as Console from "effect/Console";
14
- import * as Effect from "effect/Effect";
15
- import * as Option from "effect/Option";
16
- import { JiraAuth } from "../JiraAuth.js";
17
- // === Auth create command ===
18
- const createCommand = Command.make("create", {}, () => Effect.gen(function* () {
19
- yield* Console.log(`
20
- Creating OAuth app in Atlassian Developer Console...
21
-
22
- 1. Browser will open to create a new OAuth 2.0 (3LO) app
23
- 2. Enter app name (e.g., "Jira CLI")
24
- 3. After creation, go to "Permissions" and add:
25
-
26
- Jira API:
27
- - read:jira-work Search and read issues, comments, attachments
28
- - read:jira-user Read user info for assignee/reporter fields
29
-
30
- User Identity API:
31
- - read:me Get your account ID and email for auth
32
-
33
- 4. Go to "Authorization" and set callback URL:
34
- http://localhost:8585/callback
35
- 5. Go to "Settings" and copy Client ID and Secret
36
- 6. Run: jira auth configure --client-id <ID> --client-secret <SECRET>
37
- `);
38
- const url = "https://developer.atlassian.com/console/myapps/create-3lo-app/";
39
- yield* openBrowser(url).pipe(Effect.catch(() => Effect.void));
40
- })).pipe(Command.withDescription("Create OAuth app in Atlassian Developer Console"));
41
- // === Auth configure command ===
42
- const clientIdOption = Options.String("client-id").pipe(Options.withDescription("OAuth client ID from Atlassian Developer Console"), Options.optional);
43
- const clientSecretOption = Options.String("client-secret").pipe(Options.withDescription("OAuth client secret"), Options.optional);
44
- const configureCommand = Command.make("configure", { clientId: clientIdOption, clientSecret: clientSecretOption }, ({ clientId, clientSecret }) => Effect.gen(function* () {
45
- const auth = yield* JiraAuth;
46
- const rawClientId = Option.isSome(clientId)
47
- ? clientId.value
48
- : yield* Prompt.String({ message: "Enter OAuth client ID:" });
49
- const rawClientSecret = Option.isSome(clientSecret)
50
- ? clientSecret.value
51
- : yield* Prompt.String({ message: "Enter OAuth client secret:" });
52
- yield* auth.configure({ clientId: rawClientId, clientSecret: rawClientSecret });
53
- yield* Console.log("OAuth configured. Run 'jira auth login' to authenticate.");
54
- })).pipe(Command.withDescription("Configure OAuth client credentials"));
55
- // === Auth login command ===
56
- const siteOption = Options.String("site").pipe(Options.withDescription("Jira site URL to use (for accounts with multiple sites)"), Options.optional);
57
- const loginCommand = Command.make("login", { site: siteOption }, ({ site }) => Effect.gen(function* () {
58
- const auth = yield* JiraAuth;
59
- const result = yield* auth.login(Option.isSome(site) ? { siteUrl: site.value } : undefined);
60
- if (Array.isArray(result) && result.length > 0) {
61
- yield* Console.log("\nRe-run with --site to select a specific site.");
62
- }
63
- })).pipe(Command.withDescription("Authenticate with Atlassian via OAuth"));
64
- // === Auth logout command ===
65
- const logoutCommand = Command.make("logout", {}, () => Effect.gen(function* () {
66
- const auth = yield* JiraAuth;
67
- yield* auth.logout();
68
- yield* Console.log("Logged out");
69
- })).pipe(Command.withDescription("Remove stored authentication"));
70
- // === Auth status command ===
71
- const statusCommand = Command.make("status", {}, () => Effect.gen(function* () {
72
- const auth = yield* JiraAuth;
73
- const profile = yield* auth.getActiveProfile();
74
- if (profile) {
75
- const user = profile.token.user;
76
- const account = user ? `${user.name} (${user.email})` : "unknown user";
77
- yield* Console.log(`Active profile: ${profile.name}`);
78
- yield* Console.log(`Account: ${account}`);
79
- yield* Console.log(`Site: ${profile.token.site_url}`);
80
- yield* Console.log(`Profile ID: ${profile.id}`);
81
- }
82
- else {
83
- yield* Console.log("Not logged in. Use 'jira auth login' to authenticate.");
84
- }
85
- })).pipe(Command.withDescription("Show authentication status"));
86
- // === Auth profiles command ===
87
- const profilesCommand = Command.make("profiles", {}, () => Effect.gen(function* () {
88
- const auth = yield* JiraAuth;
89
- const [profiles, active] = yield* Effect.all([auth.listProfiles(), auth.getActiveProfile()]);
90
- if (profiles.length === 0) {
91
- yield* Console.log("No auth profiles. Use 'jira auth login' to authenticate.");
92
- return;
93
- }
94
- for (const profile of profiles) {
95
- const marker = active?.id === profile.id ? "*" : " ";
96
- yield* Console.log(`${marker} ${profile.id}`);
97
- yield* Console.log(` ${profile.name}`);
98
- yield* Console.log(` ${profile.token.site_url}`);
99
- }
100
- })).pipe(Command.withDescription("List stored auth profiles"));
101
- const profileArg = Args.String("profile").pipe(Args.withDescription("Profile ID, name, site URL, cloud ID, or account ID"));
102
- // === Auth use command ===
103
- const useCommand = Command.make("use", { profile: profileArg }, ({ profile }) => Effect.gen(function* () {
104
- const auth = yield* JiraAuth;
105
- const selected = yield* auth.switchProfile(profile);
106
- if (!selected) {
107
- yield* Console.log(`Profile not found: ${profile}`);
108
- return;
109
- }
110
- yield* Console.log(`Active profile: ${selected.name}`);
111
- })).pipe(Command.withDescription("Switch active auth profile"));
112
- // === Auth remove command ===
113
- const removeCommand = Command.make("remove", { profile: profileArg }, ({ profile }) => Effect.gen(function* () {
114
- const auth = yield* JiraAuth;
115
- const removed = yield* auth.removeProfile(profile);
116
- if (!removed) {
117
- yield* Console.log(`Profile not found: ${profile}`);
118
- return;
119
- }
120
- yield* Console.log(`Removed profile: ${removed.name}`);
121
- })).pipe(Command.withDescription("Remove stored auth profile"));
122
- // === Auth command group ===
123
- export const authCommand = Command.make("auth").pipe(Command.withDescription("Manage OAuth authentication"), Command.withSubcommands([
124
- createCommand,
125
- configureCommand,
126
- loginCommand,
127
- logoutCommand,
128
- statusCommand,
129
- profilesCommand,
130
- useCommand,
131
- removeCommand
132
- ]));
6
+ import { makeAuthCommand } from "@knpkv/atlassian-common/cli-auth";
7
+ import { JiraAuth, jiraCliDescriptor } from "../JiraAuth.js";
8
+ export const authCommand = makeAuthCommand(JiraAuth, jiraCliDescriptor, { apiSection: "Jira API" });
133
9
  //# sourceMappingURL=auth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AAClE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAE5D,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAC,QAAQ,EAAE,iBAAiB,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAA"}
@@ -11,7 +11,7 @@ import { VersionService } from "../VersionService.js";
11
11
  *
12
12
  * @category Layers
13
13
  */
14
- export declare const AppLayer: Layer.Layer<AttachmentService | import("effect/http/HttpClient").HttpClient | IssueService | JiraApiClient | JiraAuth | MarkdownWriter | SiteUrl | VersionService | NodeServices.NodeServices, import("../JiraCliError.js").AuthMissingError | import("@knpkv/atlassian-common/config").FileSystemError | import("@knpkv/atlassian-common/config").HomeDirectoryError | import("@knpkv/atlassian-common/auth").OAuthError | import("effect/PlatformError").PlatformError, never>;
14
+ export declare const AppLayer: Layer.Layer<AttachmentService | import("effect/http/HttpClient").HttpClient | IssueService | JiraApiClient | JiraAuth | MarkdownWriter | SiteUrl | VersionService | NodeServices.NodeServices, import("../JiraCliError.js").AuthMissingError | import("@knpkv/atlassian-common").OAuthError | (import("@knpkv/atlassian-common").FileSystemError | import("@knpkv/atlassian-common").HomeDirectoryError | import("effect/PlatformError").PlatformError), never>;
15
15
  /**
16
16
  * Auth-only layer for auth commands.
17
17
  *
@@ -1 +1 @@
1
- {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/commands/layers.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,YAAY,MAAM,oCAAoC,CAAA;AAClE,OAAO,EAAE,aAAa,EAAiB,MAAM,wBAAwB,CAAA;AAErE,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,iBAAiB,EAAmC,MAAM,yBAAyB,CAAA;AAC5F,OAAO,EAAE,YAAY,EAA8B,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,QAAQ,EAA0B,MAAM,gBAAgB,CAAA;AACjE,OAAO,EAAgC,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACnF,OAAO,EAAgC,cAAc,EAAE,MAAM,sBAAsB,CAAA;AA+FnF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,kdASpB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,oLAOzB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,sIAMxB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,SAAU,aAAa,CAAC,MAAM,CAAC,KAAG,MAAM,GAAG,MAAM,GAAG,SAkB5E,CAAA"}
1
+ {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/commands/layers.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,YAAY,MAAM,oCAAoC,CAAA;AAClE,OAAO,EAAE,aAAa,EAAiB,MAAM,wBAAwB,CAAA;AAErE,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,iBAAiB,EAAmC,MAAM,yBAAyB,CAAA;AAC5F,OAAO,EAAE,YAAY,EAA8B,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,QAAQ,EAA0B,MAAM,gBAAgB,CAAA;AACjE,OAAO,EAAgC,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACnF,OAAO,EAAgC,cAAc,EAAE,MAAM,sBAAsB,CAAA;AA+FnF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,icASpB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,oLAOzB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,sIAMxB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,SAAU,aAAa,CAAC,MAAM,CAAC,KAAG,MAAM,GAAG,MAAM,GAAG,SAkB5E,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knpkv/jira-cli",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "CLI tool to fetch Jira tickets and export to markdown",
5
5
  "license": "MIT",
6
6
  "author": "knpkv",
@@ -60,8 +60,8 @@
60
60
  "@effect/platform-node": "4.0.0",
61
61
  "gray-matter": "^4.0.3",
62
62
  "js-yaml": "5.4.2",
63
- "@knpkv/agent-skills": "^0.3.2",
64
- "@knpkv/atlassian-common": "1.6.0",
63
+ "@knpkv/agent-skills": "^0.3.3",
64
+ "@knpkv/atlassian-common": "1.8.0",
65
65
  "@knpkv/jira-api-client": "2.0.0"
66
66
  },
67
67
  "devDependencies": {
@@ -21,6 +21,7 @@ Use the `jira` binary for Jira Cloud issue export and release-version workflows.
21
21
  ```bash
22
22
  jira auth status
23
23
  jira auth create
24
+ jira auth manage
24
25
  jira auth configure --client-id <id> --client-secret <secret>
25
26
  jira auth login
26
27
  jira auth login --site https://example.atlassian.net
package/src/JiraAuth.ts CHANGED
@@ -16,20 +16,14 @@
16
16
  *
17
17
  * @module
18
18
  */
19
- import type { OAuthError } from "@knpkv/atlassian-common/auth"
20
- import { makeAtlassianCliAuth, NodeCliAuthLive } from "@knpkv/atlassian-common/cli-auth"
21
- import type {
22
- AuthProfile,
23
- FileSystemError,
24
- HomeDirectoryError,
25
- OAuthConfig,
26
- OAuthUser
27
- } from "@knpkv/atlassian-common/config"
19
+ import {
20
+ type AtlassianCliAuth,
21
+ type AtlassianCliDescriptor,
22
+ makeAtlassianCliAuth,
23
+ NodeCliAuthLive
24
+ } from "@knpkv/atlassian-common/cli-auth"
28
25
  import * as Context from "effect/Context"
29
- import type * as Effect from "effect/Effect"
30
26
  import * as Layer from "effect/Layer"
31
- import type * as PlatformError from "effect/PlatformError"
32
- import type * as Redacted from "effect/Redacted"
33
27
  import type { AuthMissingError } from "./JiraCliError.js"
34
28
  import { authMissing } from "./JiraCliError.js"
35
29
 
@@ -51,95 +45,15 @@ const JIRA_CLI_SCOPES = [
51
45
  "offline_access"
52
46
  ]
53
47
 
54
- /**
55
- * Options for the login method.
56
- *
57
- * @category Types
58
- */
59
- export interface LoginOptions {
60
- /** Site URL to select (for accounts with multiple sites) */
61
- readonly siteUrl?: string
62
- }
63
-
64
- /**
65
- * Information about an accessible Jira site.
66
- *
67
- * @category Types
68
- */
69
- export interface AccessibleSite {
70
- readonly id: string
71
- readonly name: string
72
- readonly url: string
73
- }
48
+ export type { AccessibleSite, LoginOptions } from "@knpkv/atlassian-common/cli-auth"
74
49
 
75
50
  /**
76
- * JiraAuth service interface.
51
+ * JiraAuth service interface: the shared Atlassian CLI auth, failing with Jira's own
52
+ * {@link AuthMissingError} when nobody is logged in.
77
53
  *
78
54
  * @category Services
79
55
  */
80
- export interface JiraAuthService {
81
- /** Configure OAuth client credentials */
82
- readonly configure: (
83
- config: OAuthConfig
84
- ) => Effect.Effect<void, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>
85
- /** Check if OAuth is configured */
86
- readonly isConfigured: () => Effect.Effect<
87
- boolean,
88
- FileSystemError | HomeDirectoryError | PlatformError.PlatformError
89
- >
90
- /** Start OAuth login flow. Returns list of sites if multiple are available. */
91
- readonly login: (
92
- options?: LoginOptions
93
- ) => Effect.Effect<
94
- ReadonlyArray<AccessibleSite> | void,
95
- OAuthError | FileSystemError | HomeDirectoryError | PlatformError.PlatformError
96
- >
97
- /** Remove stored authentication */
98
- readonly logout: () => Effect.Effect<
99
- void,
100
- OAuthError | FileSystemError | HomeDirectoryError | PlatformError.PlatformError
101
- >
102
- /** Get access token, refreshing if needed */
103
- readonly getAccessToken: () => Effect.Effect<
104
- Redacted.Redacted<string>,
105
- AuthMissingError | OAuthError | FileSystemError | HomeDirectoryError | PlatformError.PlatformError
106
- >
107
- /** Get cloud ID from stored token */
108
- readonly getCloudId: () => Effect.Effect<
109
- string,
110
- AuthMissingError | FileSystemError | HomeDirectoryError | PlatformError.PlatformError
111
- >
112
- /** Get site URL from stored token */
113
- readonly getSiteUrl: () => Effect.Effect<
114
- string,
115
- AuthMissingError | FileSystemError | HomeDirectoryError | PlatformError.PlatformError
116
- >
117
- /** Get current user info from stored token */
118
- readonly getCurrentUser: () => Effect.Effect<
119
- OAuthUser | null,
120
- FileSystemError | HomeDirectoryError | PlatformError.PlatformError
121
- >
122
- /** Get active auth profile */
123
- readonly getActiveProfile: () => Effect.Effect<
124
- AuthProfile | null,
125
- FileSystemError | HomeDirectoryError | PlatformError.PlatformError
126
- >
127
- /** List stored auth profiles */
128
- readonly listProfiles: () => Effect.Effect<
129
- ReadonlyArray<AuthProfile>,
130
- FileSystemError | HomeDirectoryError | PlatformError.PlatformError
131
- >
132
- /** Switch active profile by ID, name, site URL, cloud ID, or account ID */
133
- readonly switchProfile: (
134
- selector: string
135
- ) => Effect.Effect<AuthProfile | null, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>
136
- /** Remove stored profile by ID, name, site URL, cloud ID, or account ID */
137
- readonly removeProfile: (
138
- selector: string
139
- ) => Effect.Effect<AuthProfile | null, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>
140
- /** Check if user is logged in */
141
- readonly isLoggedIn: () => Effect.Effect<boolean, FileSystemError | HomeDirectoryError | PlatformError.PlatformError>
142
- }
56
+ export interface JiraAuthService extends AtlassianCliAuth<AuthMissingError> {}
143
57
 
144
58
  /**
145
59
  * JiraAuth service tag.
@@ -165,6 +79,13 @@ export class JiraAuth extends Context.Service<
165
79
  JiraAuthService
166
80
  >()("@knpkv/jira-cli/JiraAuth") {}
167
81
 
82
+ /** Who the Jira CLI is: shared by its auth and its `auth` commands, so both name the same scopes. */
83
+ export const jiraCliDescriptor: AtlassianCliDescriptor = {
84
+ commandName: "jira",
85
+ productName: "Jira",
86
+ scopes: JIRA_CLI_SCOPES
87
+ }
88
+
168
89
  /**
169
90
  * Layer for JiraAuth service.
170
91
  *
@@ -173,10 +94,8 @@ export class JiraAuth extends Context.Service<
173
94
  export const layer = Layer.effect(
174
95
  JiraAuth,
175
96
  makeAtlassianCliAuth({
97
+ ...jiraCliDescriptor,
176
98
  toolName: "jira-cli",
177
- commandName: "jira",
178
- productName: "Jira",
179
- scopes: JIRA_CLI_SCOPES,
180
99
  authMissing
181
100
  })
182
101
  ).pipe(Layer.provide(NodeCliAuthLive))
@@ -1,172 +1,9 @@
1
1
  /**
2
- * Auth subcommands: create, configure, login, logout, status.
3
- *
4
- * **Mental model**
5
- *
6
- * - Each command is a standalone `Command.make` that yields into `JiraAuth` service methods.
7
- * - `create` opens the Atlassian Developer Console; `configure` prompts for client ID/secret.
2
+ * `jira auth`: the shared Atlassian auth commands over {@link JiraAuth}.
8
3
  *
9
4
  * @internal
10
5
  */
11
- import { openBrowser } from "@knpkv/atlassian-common/cli-auth"
12
- import { Argument as Args, Command, Flag as Options, Prompt } from "effect/cli"
13
- import * as Console from "effect/Console"
14
- import * as Effect from "effect/Effect"
15
- import * as Option from "effect/Option"
16
- import { JiraAuth } from "../JiraAuth.js"
17
-
18
- // === Auth create command ===
19
- const createCommand = Command.make("create", {}, () =>
20
- Effect.gen(function*() {
21
- yield* Console.log(`
22
- Creating OAuth app in Atlassian Developer Console...
23
-
24
- 1. Browser will open to create a new OAuth 2.0 (3LO) app
25
- 2. Enter app name (e.g., "Jira CLI")
26
- 3. After creation, go to "Permissions" and add:
27
-
28
- Jira API:
29
- - read:jira-work Search and read issues, comments, attachments
30
- - read:jira-user Read user info for assignee/reporter fields
31
-
32
- User Identity API:
33
- - read:me Get your account ID and email for auth
34
-
35
- 4. Go to "Authorization" and set callback URL:
36
- http://localhost:8585/callback
37
- 5. Go to "Settings" and copy Client ID and Secret
38
- 6. Run: jira auth configure --client-id <ID> --client-secret <SECRET>
39
- `)
40
- const url = "https://developer.atlassian.com/console/myapps/create-3lo-app/"
41
- yield* openBrowser(url).pipe(Effect.catch(() => Effect.void))
42
- })).pipe(Command.withDescription("Create OAuth app in Atlassian Developer Console"))
43
-
44
- // === Auth configure command ===
45
- const clientIdOption = Options.String("client-id").pipe(
46
- Options.withDescription("OAuth client ID from Atlassian Developer Console"),
47
- Options.optional
48
- )
49
- const clientSecretOption = Options.String("client-secret").pipe(
50
- Options.withDescription("OAuth client secret"),
51
- Options.optional
52
- )
53
-
54
- const configureCommand = Command.make(
55
- "configure",
56
- { clientId: clientIdOption, clientSecret: clientSecretOption },
57
- ({ clientId, clientSecret }) =>
58
- Effect.gen(function*() {
59
- const auth = yield* JiraAuth
60
-
61
- const rawClientId = Option.isSome(clientId)
62
- ? clientId.value
63
- : yield* Prompt.String({ message: "Enter OAuth client ID:" })
64
- const rawClientSecret = Option.isSome(clientSecret)
65
- ? clientSecret.value
66
- : yield* Prompt.String({ message: "Enter OAuth client secret:" })
67
-
68
- yield* auth.configure({ clientId: rawClientId, clientSecret: rawClientSecret })
69
- yield* Console.log("OAuth configured. Run 'jira auth login' to authenticate.")
70
- })
71
- ).pipe(Command.withDescription("Configure OAuth client credentials"))
72
-
73
- // === Auth login command ===
74
- const siteOption = Options.String("site").pipe(
75
- Options.withDescription("Jira site URL to use (for accounts with multiple sites)"),
76
- Options.optional
77
- )
78
-
79
- const loginCommand = Command.make("login", { site: siteOption }, ({ site }) =>
80
- Effect.gen(function*() {
81
- const auth = yield* JiraAuth
82
- const result = yield* auth.login(Option.isSome(site) ? { siteUrl: site.value } : undefined)
83
- if (Array.isArray(result) && result.length > 0) {
84
- yield* Console.log("\nRe-run with --site to select a specific site.")
85
- }
86
- })).pipe(Command.withDescription("Authenticate with Atlassian via OAuth"))
87
-
88
- // === Auth logout command ===
89
- const logoutCommand = Command.make("logout", {}, () =>
90
- Effect.gen(function*() {
91
- const auth = yield* JiraAuth
92
- yield* auth.logout()
93
- yield* Console.log("Logged out")
94
- })).pipe(Command.withDescription("Remove stored authentication"))
95
-
96
- // === Auth status command ===
97
- const statusCommand = Command.make("status", {}, () =>
98
- Effect.gen(function*() {
99
- const auth = yield* JiraAuth
100
- const profile = yield* auth.getActiveProfile()
101
- if (profile) {
102
- const user = profile.token.user
103
- const account = user ? `${user.name} (${user.email})` : "unknown user"
104
- yield* Console.log(`Active profile: ${profile.name}`)
105
- yield* Console.log(`Account: ${account}`)
106
- yield* Console.log(`Site: ${profile.token.site_url}`)
107
- yield* Console.log(`Profile ID: ${profile.id}`)
108
- } else {
109
- yield* Console.log("Not logged in. Use 'jira auth login' to authenticate.")
110
- }
111
- })).pipe(Command.withDescription("Show authentication status"))
112
-
113
- // === Auth profiles command ===
114
- const profilesCommand = Command.make("profiles", {}, () =>
115
- Effect.gen(function*() {
116
- const auth = yield* JiraAuth
117
- const [profiles, active] = yield* Effect.all([auth.listProfiles(), auth.getActiveProfile()])
118
- if (profiles.length === 0) {
119
- yield* Console.log("No auth profiles. Use 'jira auth login' to authenticate.")
120
- return
121
- }
122
-
123
- for (const profile of profiles) {
124
- const marker = active?.id === profile.id ? "*" : " "
125
- yield* Console.log(`${marker} ${profile.id}`)
126
- yield* Console.log(` ${profile.name}`)
127
- yield* Console.log(` ${profile.token.site_url}`)
128
- }
129
- })).pipe(Command.withDescription("List stored auth profiles"))
130
-
131
- const profileArg = Args.String("profile").pipe(
132
- Args.withDescription("Profile ID, name, site URL, cloud ID, or account ID")
133
- )
134
-
135
- // === Auth use command ===
136
- const useCommand = Command.make("use", { profile: profileArg }, ({ profile }) =>
137
- Effect.gen(function*() {
138
- const auth = yield* JiraAuth
139
- const selected = yield* auth.switchProfile(profile)
140
- if (!selected) {
141
- yield* Console.log(`Profile not found: ${profile}`)
142
- return
143
- }
144
- yield* Console.log(`Active profile: ${selected.name}`)
145
- })).pipe(Command.withDescription("Switch active auth profile"))
146
-
147
- // === Auth remove command ===
148
- const removeCommand = Command.make("remove", { profile: profileArg }, ({ profile }) =>
149
- Effect.gen(function*() {
150
- const auth = yield* JiraAuth
151
- const removed = yield* auth.removeProfile(profile)
152
- if (!removed) {
153
- yield* Console.log(`Profile not found: ${profile}`)
154
- return
155
- }
156
- yield* Console.log(`Removed profile: ${removed.name}`)
157
- })).pipe(Command.withDescription("Remove stored auth profile"))
6
+ import { makeAuthCommand } from "@knpkv/atlassian-common/cli-auth"
7
+ import { JiraAuth, jiraCliDescriptor } from "../JiraAuth.js"
158
8
 
159
- // === Auth command group ===
160
- export const authCommand = Command.make("auth").pipe(
161
- Command.withDescription("Manage OAuth authentication"),
162
- Command.withSubcommands([
163
- createCommand,
164
- configureCommand,
165
- loginCommand,
166
- logoutCommand,
167
- statusCommand,
168
- profilesCommand,
169
- useCommand,
170
- removeCommand
171
- ])
172
- )
9
+ export const authCommand = makeAuthCommand(JiraAuth, jiraCliDescriptor, { apiSection: "Jira API" })
@@ -0,0 +1,54 @@
1
+ /** @effect-diagnostics strictEffectProvide:skip-file */
2
+ /**
3
+ * `jira auth create` must list every scope `jira auth login` requests: Atlassian rejects an
4
+ * authorization request naming a scope the app lacks, so a shorter list is a setup that cannot log in.
5
+ */
6
+ import { NodeServices } from "@effect/platform-node"
7
+ import { expect, it } from "@effect/vitest"
8
+ import { Command } from "effect/cli"
9
+ import * as Console from "effect/Console"
10
+ import * as Effect from "effect/Effect"
11
+ import { FetchHttpClient } from "effect/http"
12
+ import * as Layer from "effect/Layer"
13
+ import { ChildProcessSpawner } from "effect/process"
14
+ import * as Sink from "effect/Sink"
15
+ import * as Stream from "effect/Stream"
16
+ import { authCommand } from "../src/commands/auth.js"
17
+ import { jiraCliDescriptor, layer } from "../src/JiraAuth.js"
18
+
19
+ const browserOpens = ChildProcessSpawner.make(() =>
20
+ Effect.succeed(ChildProcessSpawner.makeHandle({
21
+ all: Stream.empty,
22
+ exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),
23
+ getInputFd: () => Sink.drain,
24
+ getOutputFd: () => Stream.empty,
25
+ isRunning: Effect.succeed(false),
26
+ kill: () => Effect.void,
27
+ pid: ChildProcessSpawner.ProcessId(1),
28
+ reref: Effect.void,
29
+ stderr: Stream.empty,
30
+ stdin: Sink.drain,
31
+ stdout: Stream.empty,
32
+ unref: Effect.succeed(Effect.void)
33
+ }))
34
+ )
35
+
36
+ it.effect("auth create lists every scope JiraAuth logs in with", () =>
37
+ Effect.gen(function*() {
38
+ const lines: Array<string> = []
39
+ const capture: Console.Console = Object.assign(Object.create(console), {
40
+ log: (...parts: ReadonlyArray<unknown>) => lines.push(parts.join(" "))
41
+ })
42
+ const cli = Command.runWith(Command.make("jira").pipe(Command.withSubcommands([authCommand])), { version: "0" })
43
+ const exit = yield* cli(["auth", "create"]).pipe(Effect.provideService(Console.Console, capture), Effect.exit)
44
+ expect(exit._tag).toBe("Success")
45
+ const output = lines.join("\n")
46
+ const permissions = jiraCliDescriptor.scopes.filter((scope) => scope !== "offline_access")
47
+ expect(permissions.length).toBeGreaterThan(2)
48
+ for (const scope of permissions) expect(output).toContain(scope)
49
+ expect(output).toContain("jira auth configure")
50
+ }).pipe(
51
+ // The command's browser launch sees the fake; the auth layer is built over real platform services.
52
+ Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, browserOpens),
53
+ Effect.provide(layer.pipe(Layer.provideMerge(Layer.mergeAll(FetchHttpClient.layer, NodeServices.layer))))
54
+ ))