@knpkv/atlassian-common 1.8.0 → 1.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @knpkv/atlassian-common
2
2
 
3
+ ## 1.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#479](https://github.com/knpkv/npm/pull/479) [`1ef1847`](https://github.com/knpkv/npm/commit/1ef1847c867f83de237dec1c454b6bb5e40183ef) Thanks [@konopkov](https://github.com/konopkov)! - One error report for every Atlassian CLI (`jira`, `confluence`, `atlassian`), from the new `@knpkv/atlassian-common/cli` subpath (`handleCliError`, `withCliErrorHandling`, `Verbose`, `commandArgs`, `verboseRequested`).
8
+
9
+ - A failed command prints the error's message; a defect prints `Error: <message>`. The full cause, with stack traces, prints only with the new global `--verbose` flag or `DEBUG=1`.
10
+ - jira: stderr no longer prints the pretty-printed cause and stack trace by default.
11
+ - atlassian: tagged errors print their message instead of `String(error)`, and defects are reported too.
12
+ - Help and usage errors that the CLI already rendered are not printed a second time. Exit codes are unchanged.
13
+
3
14
  ## 1.8.0
4
15
 
5
16
  ### Minor Changes
package/dist/bin.js CHANGED
@@ -6,6 +6,7 @@ import * as Effect from "effect/Effect";
6
6
  import * as Layer from "effect/Layer";
7
7
  import * as Stdio from "effect/Stdio";
8
8
  import pkg from "../package.json" with { type: "json" };
9
+ import { Verbose, withCliErrorHandling } from "./cli/index.js";
9
10
  import { HomeDirectoryLive, inspectAllToolProfiles, migrateLegacyProfiles, refreshActiveProfiles, useProfileForAllTools } from "./config/index.js";
10
11
  const profileArg = Args.String("profile").pipe(Args.withDescription("Profile ID, name, site URL, cloud ID, or account ID"));
11
12
  const printStatuses = (statuses) => Effect.gen(function* () {
@@ -47,12 +48,12 @@ const profiles = Command.make("profiles").pipe(Command.withDescription("Manage s
47
48
  const authRefresh = Command.make("refresh", {}, () => refreshActiveProfiles().pipe(Effect.flatMap(printStatuses)))
48
49
  .pipe(Command.withDescription("Refresh expired active OAuth tokens"));
49
50
  const auth = Command.make("auth").pipe(Command.withDescription("Manage Atlassian authentication"), Command.withSubcommands([authRefresh]));
50
- const atlassian = Command.make("atlassian", {}, () => Console.log("Usage: atlassian profiles|auth")).pipe(Command.withDescription("Unified Atlassian profile manager"), Command.withSubcommands([profiles, auth]));
51
+ const atlassian = Command.make("atlassian", {}, () => Console.log("Usage: atlassian profiles|auth")).pipe(Command.withDescription("Unified Atlassian profile manager"), Command.withGlobalFlags([Verbose]), Command.withSubcommands([profiles, auth]));
51
52
  const cli = Command.runWith(atlassian, { version: pkg.version });
52
53
  const program = Effect.gen(function* () {
53
54
  const stdio = yield* Stdio.Stdio;
54
55
  const args = yield* stdio.args;
55
56
  return yield* cli(args);
56
- }).pipe(Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerFetch, HomeDirectoryLive)), Effect.catch((error) => Console.error(String(error)).pipe(Effect.andThen(Effect.fail(error)))));
57
+ }).pipe(withCliErrorHandling, Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerFetch, HomeDirectoryLive)));
57
58
  NodeRuntime.runMain(program, { disableErrorReporting: true });
58
59
  //# sourceMappingURL=bin.js.map
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * How an Atlassian command-line tool reports a failed run.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **The message is the report.** A failure prints its message; a defect prints `Error: <message>`;
7
+ * an interruption prints `Interrupted`. The full cause, with stack traces, is for whoever asked for
8
+ * it: `--verbose`, or `DEBUG=1` in the environment.
9
+ * - **What the CLI already showed is not repeated.** `Command.runWith` renders help and usage errors
10
+ * itself and then fails with them, so those reasons are skipped unless verbose.
11
+ * - **The exit code is untouched.** The cause is reported and re-failed as it was, so a help request
12
+ * still exits 0 and every failure still exits non-zero.
13
+ *
14
+ * @module
15
+ */
16
+ import * as Cause from "effect/Cause";
17
+ import { GlobalFlag } from "effect/cli";
18
+ import * as Effect from "effect/Effect";
19
+ import * as Stdio from "effect/Stdio";
20
+ /** `--verbose`: print the full cause, with stack traces, when a command fails. Register on the root command. */
21
+ export declare const Verbose: GlobalFlag.Setting<"verbose", boolean>;
22
+ /** Whether `args` turn {@link Verbose} on, as the flag parser would read them. */
23
+ export declare const verboseFlagIn: (args: ReadonlyArray<string>) => boolean;
24
+ /**
25
+ * `args` without the {@link Verbose} flag and the value it consumed, for code that routes on the
26
+ * command words before the CLI parses them, such as choosing which service layer to build.
27
+ */
28
+ export declare const commandArgs: (args: ReadonlyArray<string>) => ReadonlyArray<string>;
29
+ /** `--verbose` in `args`, or `DEBUG=1`. Only `1`: `DEBUG` is also the debug package's namespace list. */
30
+ export declare const verboseRequested: (args: ReadonlyArray<string>) => Effect.Effect<boolean>;
31
+ /** Print a failed run's cause to stderr: each failure's message, or the full cause when verbose. */
32
+ export declare const handleCliError: <E>(cause: Cause.Cause<E>, options: {
33
+ readonly verbose: boolean;
34
+ }) => Effect.Effect<void>;
35
+ /**
36
+ * Report a CLI program's failure with {@link handleCliError} and fail with the same cause. Apply it
37
+ * before providing `Stdio`, which it reads the arguments from.
38
+ */
39
+ export declare const withCliErrorHandling: <A, E, R>(program: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R | Stdio.Stdio>;
40
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAkB,UAAU,EAAE,MAAM,YAAY,CAAA;AAGvD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,gHAAgH;AAChH,eAAO,MAAM,OAAO,wCAKlB,CAAA;AAiDF,kFAAkF;AAClF,eAAO,MAAM,aAAa,SAAU,aAAa,CAAC,MAAM,CAAC,KAAG,OAA6C,CAAA;AAEzG;;;GAGG;AACH,eAAO,MAAM,WAAW,SAAU,aAAa,CAAC,MAAM,CAAC,KAAG,aAAa,CAAC,MAAM,CAA2B,CAAA;AAEzG,yGAAyG;AACzG,eAAO,MAAM,gBAAgB,SAAU,aAAa,CAAC,MAAM,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAMtC,CAAA;AAc9C,oGAAoG;AACpG,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW;IAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAS3F,CAAA;AASzB;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,WACjC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAC0E,CAAA"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * How an Atlassian command-line tool reports a failed run.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **The message is the report.** A failure prints its message; a defect prints `Error: <message>`;
7
+ * an interruption prints `Interrupted`. The full cause, with stack traces, is for whoever asked for
8
+ * it: `--verbose`, or `DEBUG=1` in the environment.
9
+ * - **What the CLI already showed is not repeated.** `Command.runWith` renders help and usage errors
10
+ * itself and then fails with them, so those reasons are skipped unless verbose.
11
+ * - **The exit code is untouched.** The cause is reported and re-failed as it was, so a help request
12
+ * still exits 0 and every failure still exits non-zero.
13
+ *
14
+ * @module
15
+ */
16
+ import * as Cause from "effect/Cause";
17
+ import { CliError, Flag, GlobalFlag } from "effect/cli";
18
+ import * as Config from "effect/Config";
19
+ import * as Console from "effect/Console";
20
+ import * as Effect from "effect/Effect";
21
+ import * as Option from "effect/Option";
22
+ import * as Predicate from "effect/Predicate";
23
+ import * as Stdio from "effect/Stdio";
24
+ /** `--verbose`: print the full cause, with stack traces, when a command fails. Register on the root command. */
25
+ export const Verbose = GlobalFlag.Setting("verbose")({
26
+ flag: Flag.Boolean("verbose").pipe(Flag.withDefault(false), Flag.withDescription("Print the full cause, with stack traces, when a command fails"))
27
+ });
28
+ const truthy = new Set(["true", "1", "y", "yes", "on"]);
29
+ const falsy = new Set(["false", "0", "n", "no", "off"]);
30
+ /** Exactly the literals `effect/cli` accepts for a boolean: lowercase only, so `FALSE` is not one. */
31
+ const booleanLiteral = (token) => token === undefined ? undefined : truthy.has(token) ? true : falsy.has(token) ? false : undefined;
32
+ /**
33
+ * Read {@link Verbose} out of `args` the way `effect/cli` parses a boolean flag: only before `--`;
34
+ * `--verbose` may take a following boolean literal (`--verbose false`), `--verbose=<boolean>` and
35
+ * `--no-verbose` are the other spellings, and the first occurrence decides the value.
36
+ */
37
+ const scanVerbose = (args) => {
38
+ const end = args.indexOf("--");
39
+ const head = end === -1 ? args : args.slice(0, end);
40
+ const tail = end === -1 ? [] : args.slice(end);
41
+ const rest = [];
42
+ let verbose;
43
+ const settle = (value) => {
44
+ if (verbose === undefined)
45
+ verbose = value;
46
+ };
47
+ for (let index = 0; index < head.length; index++) {
48
+ const token = head[index];
49
+ if (token === "--verbose") {
50
+ const literal = booleanLiteral(head[index + 1]);
51
+ if (literal !== undefined)
52
+ index++;
53
+ settle(literal ?? true);
54
+ }
55
+ else if (token === "--no-verbose") {
56
+ settle(false);
57
+ }
58
+ else if (token.startsWith("--verbose=")) {
59
+ // A value that is not a boolean makes the parser fail; it never turns verbose on.
60
+ const literal = booleanLiteral(token.slice("--verbose=".length));
61
+ if (literal !== undefined)
62
+ settle(literal);
63
+ }
64
+ else {
65
+ rest.push(token);
66
+ }
67
+ }
68
+ return { rest: [...rest, ...tail], verbose };
69
+ };
70
+ /** Whether `args` turn {@link Verbose} on, as the flag parser would read them. */
71
+ export const verboseFlagIn = (args) => scanVerbose(args).verbose === true;
72
+ /**
73
+ * `args` without the {@link Verbose} flag and the value it consumed, for code that routes on the
74
+ * command words before the CLI parses them, such as choosing which service layer to build.
75
+ */
76
+ export const commandArgs = (args) => scanVerbose(args).rest;
77
+ /** `--verbose` in `args`, or `DEBUG=1`. Only `1`: `DEBUG` is also the debug package's namespace list. */
78
+ export const verboseRequested = (args) => verboseFlagIn(args)
79
+ ? Effect.succeed(true)
80
+ : Effect.gen(function* () {
81
+ const debug = yield* Config.option(Config.String("DEBUG"));
82
+ return Option.getOrUndefined(debug) === "1";
83
+ }).pipe(Effect.orElseSucceed(() => false));
84
+ const describe = (error) => {
85
+ if (Predicate.hasProperty(error, "message") && Predicate.isString(error.message) && error.message !== "") {
86
+ return error.message;
87
+ }
88
+ if (Predicate.hasProperty(error, "_tag") && Predicate.isString(error._tag))
89
+ return error._tag;
90
+ return String(error);
91
+ };
92
+ /** `Command.runWith` prints help and user errors itself before failing with them; nothing else. */
93
+ const renderedByRunner = (error) => CliError.isCliError(error) && (error._tag === "ShowHelp" || error._tag === "UserError");
94
+ /** Print a failed run's cause to stderr: each failure's message, or the full cause when verbose. */
95
+ export const handleCliError = (cause, options) => options.verbose
96
+ ? Console.error(Cause.pretty(cause))
97
+ : Effect.forEach(cause.reasons, (reason) => {
98
+ if (Cause.isFailReason(reason)) {
99
+ return renderedByRunner(reason.error) ? Effect.void : Console.error(describe(reason.error));
100
+ }
101
+ if (Cause.isDieReason(reason))
102
+ return Console.error(`Error: ${describe(reason.defect)}`);
103
+ return Console.error("Interrupted");
104
+ }, { discard: true });
105
+ const reportFailure = (cause) => Effect.gen(function* () {
106
+ const stdio = yield* Stdio.Stdio;
107
+ const verbose = yield* stdio.args.pipe(Effect.flatMap(verboseRequested), Effect.orElseSucceed(() => false));
108
+ yield* handleCliError(cause, { verbose });
109
+ });
110
+ /**
111
+ * Report a CLI program's failure with {@link handleCliError} and fail with the same cause. Apply it
112
+ * before providing `Stdio`, which it reads the arguments from.
113
+ */
114
+ export const withCliErrorHandling = (program) => program.pipe(Effect.catchCause((cause) => reportFailure(cause).pipe(Effect.andThen(Effect.failCause(cause)))));
115
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knpkv/atlassian-common",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Shared utilities for Atlassian tools (AST, serializers, auth, config)",
5
5
  "license": "MIT",
6
6
  "author": "knpkv",
@@ -42,6 +42,10 @@
42
42
  "import": "./dist/auth/index.js",
43
43
  "types": "./dist/auth/index.d.ts"
44
44
  },
45
+ "./cli": {
46
+ "import": "./dist/cli/index.js",
47
+ "types": "./dist/cli/index.d.ts"
48
+ },
45
49
  "./cli-auth": {
46
50
  "import": "./dist/cli-auth/index.js",
47
51
  "types": "./dist/cli-auth/index.d.ts"
package/src/bin.ts CHANGED
@@ -6,6 +6,7 @@ import * as Effect from "effect/Effect"
6
6
  import * as Layer from "effect/Layer"
7
7
  import * as Stdio from "effect/Stdio"
8
8
  import pkg from "../package.json" with { type: "json" }
9
+ import { Verbose, withCliErrorHandling } from "./cli/index.js"
9
10
  import {
10
11
  HomeDirectoryLive,
11
12
  inspectAllToolProfiles,
@@ -90,6 +91,7 @@ const auth = Command.make("auth").pipe(
90
91
 
91
92
  const atlassian = Command.make("atlassian", {}, () => Console.log("Usage: atlassian profiles|auth")).pipe(
92
93
  Command.withDescription("Unified Atlassian profile manager"),
94
+ Command.withGlobalFlags([Verbose]),
93
95
  Command.withSubcommands([profiles, auth])
94
96
  )
95
97
 
@@ -99,15 +101,13 @@ const program = Effect.gen(function*() {
99
101
  const args = yield* stdio.args
100
102
  return yield* cli(args)
101
103
  }).pipe(
104
+ withCliErrorHandling,
102
105
  Effect.provide(
103
106
  Layer.mergeAll(
104
107
  NodeServices.layer,
105
108
  NodeHttpClient.layerFetch,
106
109
  HomeDirectoryLive
107
110
  )
108
- ),
109
- Effect.catch(<UnparsedInput>(error: UnparsedInput) =>
110
- Console.error(String(error)).pipe(Effect.andThen(Effect.fail(error)))
111
111
  )
112
112
  )
113
113
 
@@ -0,0 +1,136 @@
1
+ /**
2
+ * How an Atlassian command-line tool reports a failed run.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **The message is the report.** A failure prints its message; a defect prints `Error: <message>`;
7
+ * an interruption prints `Interrupted`. The full cause, with stack traces, is for whoever asked for
8
+ * it: `--verbose`, or `DEBUG=1` in the environment.
9
+ * - **What the CLI already showed is not repeated.** `Command.runWith` renders help and usage errors
10
+ * itself and then fails with them, so those reasons are skipped unless verbose.
11
+ * - **The exit code is untouched.** The cause is reported and re-failed as it was, so a help request
12
+ * still exits 0 and every failure still exits non-zero.
13
+ *
14
+ * @module
15
+ */
16
+ import * as Cause from "effect/Cause"
17
+ import { CliError, Flag, GlobalFlag } from "effect/cli"
18
+ import * as Config from "effect/Config"
19
+ import * as Console from "effect/Console"
20
+ import * as Effect from "effect/Effect"
21
+ import * as Option from "effect/Option"
22
+ import * as Predicate from "effect/Predicate"
23
+ import * as Stdio from "effect/Stdio"
24
+
25
+ /** `--verbose`: print the full cause, with stack traces, when a command fails. Register on the root command. */
26
+ export const Verbose = GlobalFlag.Setting("verbose")({
27
+ flag: Flag.Boolean("verbose").pipe(
28
+ Flag.withDefault(false),
29
+ Flag.withDescription("Print the full cause, with stack traces, when a command fails")
30
+ )
31
+ })
32
+
33
+ const truthy = new Set(["true", "1", "y", "yes", "on"])
34
+ const falsy = new Set(["false", "0", "n", "no", "off"])
35
+
36
+ /** Exactly the literals `effect/cli` accepts for a boolean: lowercase only, so `FALSE` is not one. */
37
+ const booleanLiteral = (token: string | undefined): boolean | undefined =>
38
+ token === undefined ? undefined : truthy.has(token) ? true : falsy.has(token) ? false : undefined
39
+
40
+ interface VerboseScan {
41
+ /** The value the flag parser settles on, or undefined when the flag is absent. */
42
+ readonly verbose: boolean | undefined
43
+ /** `args` with every token the flag consumed removed. */
44
+ readonly rest: ReadonlyArray<string>
45
+ }
46
+
47
+ /**
48
+ * Read {@link Verbose} out of `args` the way `effect/cli` parses a boolean flag: only before `--`;
49
+ * `--verbose` may take a following boolean literal (`--verbose false`), `--verbose=<boolean>` and
50
+ * `--no-verbose` are the other spellings, and the first occurrence decides the value.
51
+ */
52
+ const scanVerbose = (args: ReadonlyArray<string>): VerboseScan => {
53
+ const end = args.indexOf("--")
54
+ const head = end === -1 ? args : args.slice(0, end)
55
+ const tail = end === -1 ? [] : args.slice(end)
56
+ const rest: Array<string> = []
57
+ let verbose: boolean | undefined
58
+ const settle = (value: boolean) => {
59
+ if (verbose === undefined) verbose = value
60
+ }
61
+ for (let index = 0; index < head.length; index++) {
62
+ const token = head[index]!
63
+ if (token === "--verbose") {
64
+ const literal = booleanLiteral(head[index + 1])
65
+ if (literal !== undefined) index++
66
+ settle(literal ?? true)
67
+ } else if (token === "--no-verbose") {
68
+ settle(false)
69
+ } else if (token.startsWith("--verbose=")) {
70
+ // A value that is not a boolean makes the parser fail; it never turns verbose on.
71
+ const literal = booleanLiteral(token.slice("--verbose=".length))
72
+ if (literal !== undefined) settle(literal)
73
+ } else {
74
+ rest.push(token)
75
+ }
76
+ }
77
+ return { rest: [...rest, ...tail], verbose }
78
+ }
79
+
80
+ /** Whether `args` turn {@link Verbose} on, as the flag parser would read them. */
81
+ export const verboseFlagIn = (args: ReadonlyArray<string>): boolean => scanVerbose(args).verbose === true
82
+
83
+ /**
84
+ * `args` without the {@link Verbose} flag and the value it consumed, for code that routes on the
85
+ * command words before the CLI parses them, such as choosing which service layer to build.
86
+ */
87
+ export const commandArgs = (args: ReadonlyArray<string>): ReadonlyArray<string> => scanVerbose(args).rest
88
+
89
+ /** `--verbose` in `args`, or `DEBUG=1`. Only `1`: `DEBUG` is also the debug package's namespace list. */
90
+ export const verboseRequested = (args: ReadonlyArray<string>): Effect.Effect<boolean> =>
91
+ verboseFlagIn(args)
92
+ ? Effect.succeed(true)
93
+ : Effect.gen(function*() {
94
+ const debug = yield* Config.option(Config.String("DEBUG"))
95
+ return Option.getOrUndefined(debug) === "1"
96
+ }).pipe(Effect.orElseSucceed(() => false))
97
+
98
+ const describe = <E>(error: E): string => {
99
+ if (Predicate.hasProperty(error, "message") && Predicate.isString(error.message) && error.message !== "") {
100
+ return error.message
101
+ }
102
+ if (Predicate.hasProperty(error, "_tag") && Predicate.isString(error._tag)) return error._tag
103
+ return String(error)
104
+ }
105
+
106
+ /** `Command.runWith` prints help and user errors itself before failing with them; nothing else. */
107
+ const renderedByRunner = <E>(error: E): boolean =>
108
+ CliError.isCliError(error) && (error._tag === "ShowHelp" || error._tag === "UserError")
109
+
110
+ /** Print a failed run's cause to stderr: each failure's message, or the full cause when verbose. */
111
+ export const handleCliError = <E>(cause: Cause.Cause<E>, options: { readonly verbose: boolean }): Effect.Effect<void> =>
112
+ options.verbose
113
+ ? Console.error(Cause.pretty(cause))
114
+ : Effect.forEach(cause.reasons, (reason) => {
115
+ if (Cause.isFailReason(reason)) {
116
+ return renderedByRunner(reason.error) ? Effect.void : Console.error(describe(reason.error))
117
+ }
118
+ if (Cause.isDieReason(reason)) return Console.error(`Error: ${describe(reason.defect)}`)
119
+ return Console.error("Interrupted")
120
+ }, { discard: true })
121
+
122
+ const reportFailure = <E>(cause: Cause.Cause<E>): Effect.Effect<void, never, Stdio.Stdio> =>
123
+ Effect.gen(function*() {
124
+ const stdio = yield* Stdio.Stdio
125
+ const verbose = yield* stdio.args.pipe(Effect.flatMap(verboseRequested), Effect.orElseSucceed(() => false))
126
+ yield* handleCliError(cause, { verbose })
127
+ })
128
+
129
+ /**
130
+ * Report a CLI program's failure with {@link handleCliError} and fail with the same cause. Apply it
131
+ * before providing `Stdio`, which it reads the arguments from.
132
+ */
133
+ export const withCliErrorHandling = <A, E, R>(
134
+ program: Effect.Effect<A, E, R>
135
+ ): Effect.Effect<A, E, R | Stdio.Stdio> =>
136
+ program.pipe(Effect.catchCause((cause) => reportFailure(cause).pipe(Effect.andThen(Effect.failCause(cause)))))
@@ -0,0 +1,145 @@
1
+ /** @effect-diagnostics strictEffectProvide:skip-file */
2
+ /**
3
+ * How every Atlassian CLI reports a failed run: the message by default, the full cause when asked,
4
+ * nothing for what `Command.runWith` already rendered, and the exit unchanged.
5
+ */
6
+ import { NodeServices } from "@effect/platform-node"
7
+ import { describe, expect, it } from "@effect/vitest"
8
+ import { ConfigProvider } from "effect"
9
+ import * as Cause from "effect/Cause"
10
+ import { Argument, CliError, Command } from "effect/cli"
11
+ import * as Console from "effect/Console"
12
+ import * as Effect from "effect/Effect"
13
+ import * as Exit from "effect/Exit"
14
+ import * as Schema from "effect/Schema"
15
+ import * as Stdio from "effect/Stdio"
16
+ import {
17
+ commandArgs,
18
+ handleCliError,
19
+ Verbose,
20
+ verboseFlagIn,
21
+ verboseRequested,
22
+ withCliErrorHandling
23
+ } from "../src/cli/index.js"
24
+
25
+ class ProfileGone extends Schema.TaggedError<ProfileGone>()("ProfileGone", { message: Schema.String }) {}
26
+ class Bare extends Schema.TaggedError<Bare>()("Bare", {}) {}
27
+
28
+ const stderrOf = <E>(cause: Cause.Cause<E>, verbose: boolean) =>
29
+ Effect.gen(function*() {
30
+ const lines: Array<string> = []
31
+ const capture: Console.Console = Object.assign(Object.create(console), {
32
+ error: (...parts: ReadonlyArray<unknown>) => lines.push(parts.join(" "))
33
+ })
34
+ yield* handleCliError(cause, { verbose }).pipe(Effect.provideService(Console.Console, capture))
35
+ return lines
36
+ })
37
+
38
+ describe("handleCliError", () => {
39
+ it.effect("prints a failure's message, a bare tagged error's tag, a defect and an interruption", () =>
40
+ Effect.gen(function*() {
41
+ expect(yield* stderrOf(Cause.fail(new ProfileGone({ message: "Profile not found: x" })), false)).toEqual([
42
+ "Profile not found: x"
43
+ ])
44
+ expect(yield* stderrOf(Cause.fail(new Bare()), false)).toEqual(["Bare"])
45
+ expect(yield* stderrOf(Cause.die(new Error("boom")), false)).toEqual(["Error: boom"])
46
+ expect(yield* stderrOf(Cause.interrupt(), false)).toEqual(["Interrupted"])
47
+ }))
48
+
49
+ it.effect("skips what Command.runWith already rendered", () =>
50
+ Effect.gen(function*() {
51
+ const help = new CliError.ShowHelp({ commandPath: ["jira"], errors: [] })
52
+ expect(yield* stderrOf(Cause.fail(help), false)).toEqual([])
53
+ }))
54
+
55
+ it.effect("prints the full cause, stack included, when verbose", () =>
56
+ Effect.gen(function*() {
57
+ const lines = yield* stderrOf(Cause.die(new Error("boom")), true)
58
+ expect(lines.join("\n")).toContain("boom")
59
+ expect(lines.join("\n")).toContain("at ")
60
+ }))
61
+ })
62
+
63
+ describe("verbose", () => {
64
+ // The scanner runs before the CLI parses, so it must agree with the parser it stands in for.
65
+ const parsed = (args: ReadonlyArray<string>) =>
66
+ Effect.gen(function*() {
67
+ const seen: Array<boolean> = []
68
+ const probe = Command.make("probe", { words: Argument.String("word").pipe(Argument.variadic()) }, () =>
69
+ Effect.gen(function*() {
70
+ seen.push(yield* Verbose)
71
+ }))
72
+ const root = Command.make("tool").pipe(Command.withSubcommands([probe]), Command.withGlobalFlags([Verbose]))
73
+ const exit = yield* Command.runWith(root, { version: "0" })(args).pipe(
74
+ Effect.provide(NodeServices.layer),
75
+ Effect.exit
76
+ )
77
+ return { exit, verbose: seen[0] }
78
+ })
79
+
80
+ it.effect("reads --verbose and routes the command words exactly as the parser does", () =>
81
+ Effect.gen(function*() {
82
+ const cases: ReadonlyArray<ReadonlyArray<string>> = [
83
+ ["probe", "x"],
84
+ ["--verbose", "probe", "x"],
85
+ ["--verbose", "false", "probe", "x"],
86
+ ["--verbose", "true", "probe", "x"],
87
+ ["--verbose=false", "probe", "x"],
88
+ ["--verbose=yes", "probe", "x"],
89
+ ["probe", "x", "--verbose"],
90
+ ["--verbose", "--no-verbose", "probe", "x"],
91
+ ["--no-verbose", "--verbose", "probe", "x"],
92
+ ["probe", "--", "--verbose"],
93
+ ["probe", "x", "--verbose", "FALSE"],
94
+ ["probe", "x", "--verbose", "True"]
95
+ ]
96
+ for (const args of cases) {
97
+ const { exit, verbose } = yield* parsed(args)
98
+ expect(Exit.isSuccess(exit), JSON.stringify(args)).toBe(true)
99
+ expect(verboseFlagIn(args), JSON.stringify(args)).toBe(verbose)
100
+ expect(commandArgs(args)[0], JSON.stringify(args)).toBe("probe")
101
+ // A literal the parser does not consume stays a positional word.
102
+ for (const word of ["FALSE", "True"]) {
103
+ if (args.includes(word)) expect(commandArgs(args), JSON.stringify(args)).toContain(word)
104
+ }
105
+ }
106
+ }))
107
+
108
+ it.effect("does not report verbose for a value the parser rejects, and the rejection is printed", () =>
109
+ Effect.gen(function*() {
110
+ expect(verboseFlagIn(["--verbose=bogus", "probe"])).toBe(false)
111
+ expect(verboseFlagIn(["--verbose=FALSE", "probe"])).toBe(false)
112
+ const { exit } = yield* parsed(["--verbose=bogus", "probe", "x"])
113
+ expect(Exit.isFailure(exit)).toBe(true)
114
+ if (Exit.isFailure(exit)) {
115
+ const lines = yield* stderrOf(exit.cause, false)
116
+ expect(lines).toHaveLength(1)
117
+ expect(lines[0]).toContain("bogus")
118
+ }
119
+ }))
120
+
121
+ it.effect("turns on for DEBUG=1 only", () =>
122
+ Effect.gen(function*() {
123
+ const withEnv = (env: Record<string, string>) =>
124
+ verboseRequested([]).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env }))))
125
+ expect(yield* withEnv({ DEBUG: "1" })).toBe(true)
126
+ expect(yield* withEnv({ DEBUG: "express:*" })).toBe(false)
127
+ expect(yield* withEnv({})).toBe(false)
128
+ }))
129
+
130
+ it.effect("reports with --verbose from the arguments, and fails with the same cause", () =>
131
+ Effect.gen(function*() {
132
+ const lines: Array<string> = []
133
+ const capture: Console.Console = Object.assign(Object.create(console), {
134
+ error: (...parts: ReadonlyArray<unknown>) => lines.push(parts.join(" "))
135
+ })
136
+ const failure = new ProfileGone({ message: "Profile not found: x" })
137
+ const exit = yield* withCliErrorHandling(Effect.fail(failure)).pipe(
138
+ Effect.provideService(Console.Console, capture),
139
+ Effect.provide(Stdio.layerTest({ args: Effect.succeed(["auth", "use", "x", "--verbose"]) })),
140
+ Effect.exit
141
+ )
142
+ expect(Exit.isFailure(exit) && Cause.squash(exit.cause)).toBe(failure)
143
+ expect(lines.join("\n")).toContain("ProfileGone")
144
+ }))
145
+ })
@@ -4,6 +4,7 @@ import { describe, expect, it } from "@effect/vitest"
4
4
  import * as Effect from "effect/Effect"
5
5
  import * as FileSystem from "effect/FileSystem"
6
6
  import * as Path from "effect/Path"
7
+ import * as Predicate from "effect/Predicate"
7
8
  import { ChildProcess, ChildProcessSpawner } from "effect/process"
8
9
  import * as Schema from "effect/Schema"
9
10
  import * as Stream from "effect/Stream"
@@ -19,10 +20,11 @@ const Findings = Schema.fromJsonString(Schema.Array(Schema.Struct({ file: Schema
19
20
  describe("CLI auth guardrails", () => {
20
21
  for (const [name, config] of Object.entries(configs)) {
21
22
  it(`${name} resolves CLI auth from source rather than stale build output`, () => {
22
- expect(config.resolve?.alias).toHaveProperty(
23
- "@knpkv/atlassian-common/cli-auth",
24
- new URL("../src/cli-auth/index.ts", import.meta.url).pathname
25
- )
23
+ const aliases = config.resolve?.alias
24
+ const entries = Array.isArray(aliases) ? aliases : []
25
+ const specifier = "@knpkv/atlassian-common/cli-auth"
26
+ const cliAuth = entries.find(({ find }) => Predicate.isString(find) ? find === specifier : find.test(specifier))
27
+ expect(cliAuth?.replacement).toBe(new URL("../src/cli-auth/index.ts", import.meta.url).pathname)
26
28
  })
27
29
  }
28
30
 
package/vitest.config.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { defineConfig } from "vitest/config"
2
+ import { workspaceSourceAlias } from "../../vitest.workspace-sources.ts"
2
3
 
3
4
  export default defineConfig({
5
+ resolve: { alias: [...workspaceSourceAlias] },
4
6
  test: {
5
7
  include: ["test/**/*.test.ts"],
6
8
  globals: true,