@devflow-one/cli 0.1.10 → 0.1.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devflow-one/cli",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Terminal CLI wrapping the local DevFlow gateway so shell scripts can trigger repo actions.",
5
5
  "keywords": [
6
6
  "devflow",
package/src/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { parseArgs } from "./args.js";
6
6
  import { resolveConfig, ConfigError } from "./config.js";
7
- import { gatewayRequest, GatewayError } from "./client.js";
7
+ import { gatewayRequest, GatewayError, retryHint } from "./client.js";
8
8
  import { resolveCommand, listCommands, UsageError } from "./commands.js";
9
9
  import {
10
10
  createContextQuery,
@@ -86,17 +86,21 @@ function printWarnings(result, stderr) {
86
86
  }
87
87
  }
88
88
 
89
- /** Print an error, honoring --json, to stderr. */
90
- function printError(err, json, stderr) {
89
+ /** Print an error (plus an optional retry hint), honoring --json, to stderr. */
90
+ function printError(err, json, stderr, hint) {
91
91
  const code = err instanceof GatewayError ? err.code : err.name;
92
92
 
93
93
  if (json) {
94
- stderr(JSON.stringify({ ok: false, error: { code, message: err.message } }, null, 2));
94
+ const error = { code, message: err.message, delivered: err.delivered, hint };
95
+
96
+ stderr(JSON.stringify({ ok: false, error }, null, 2));
95
97
 
96
98
  return;
97
99
  }
98
100
 
99
101
  stderr(`error${code ? ` [${code}]` : ""}: ${err.message}`);
102
+
103
+ if (hint) stderr(`hint: ${hint}`);
100
104
  }
101
105
 
102
106
  /**
@@ -231,7 +235,7 @@ export async function run(argv, io = {}) {
231
235
  return 0;
232
236
  } catch (err) {
233
237
  if (err instanceof UsageError || err instanceof GatewayError) {
234
- printError(err, Boolean(flags.json), stderr);
238
+ printError(err, Boolean(flags.json), stderr, retryHint(err, command.retryUnsafe));
235
239
 
236
240
  return 1;
237
241
  }
package/src/client.js CHANGED
@@ -9,15 +9,34 @@ export class GatewayError extends Error {
9
9
  * @param {string} [code]
10
10
  * @param {unknown} [result] the envelope's `result`, which some failures still
11
11
  * carry — `/context` reports its ambiguity candidates there.
12
+ * @param {boolean} [delivered] on a TIMEOUT, whether the profile had already
13
+ * picked the action up (undefined from daemons that predate the flag).
12
14
  */
13
- constructor(message, code, result) {
15
+ constructor(message, code, result, delivered) {
14
16
  super(message);
15
17
  this.name = "GatewayError";
16
18
  this.code = code;
17
19
  this.result = result;
20
+ this.delivered = delivered;
18
21
  }
19
22
  }
20
23
 
24
+ export const RETRY_HINT =
25
+ "The action may have completed — verify (e.g. findPrByIssueKey) before retrying.";
26
+
27
+ /**
28
+ * The "verify before retrying" hint for a TIMEOUT of a retry-unsafe action
29
+ * that may already have run; undefined when a retry is safe.
30
+ * @param {unknown} err
31
+ * @param {boolean} [retryUnsafe]
32
+ */
33
+ export function retryHint(err, retryUnsafe) {
34
+ const mayHaveRun =
35
+ err instanceof GatewayError && err.code === "TIMEOUT" && err.delivered !== false;
36
+
37
+ return retryUnsafe && mayHaveRun ? RETRY_HINT : undefined;
38
+ }
39
+
21
40
  /**
22
41
  * Turn a network-level failure into an actionable message. ECONNREFUSED almost
23
42
  * always means the daemon isn't running.
@@ -83,5 +102,6 @@ export async function gatewayRequest(config, method, path, body) {
83
102
  error.message ?? `Gateway error (HTTP ${response.status}).`,
84
103
  error.code,
85
104
  envelope?.result,
105
+ error.delivered,
86
106
  );
87
107
  }
package/src/commands.js CHANGED
@@ -93,6 +93,7 @@ export class UsageError extends Error {
93
93
  * @property {string} [path] For GET commands.
94
94
  * @property {string} [action] Gateway action name for POST commands.
95
95
  * @property {boolean} [destructive] Requires --force/--yes (gated by cli.js).
96
+ * @property {boolean} [retryUnsafe] A retry may repeat its side effect; a TIMEOUT warns to verify first.
96
97
  * @property {(pos: string[], flags: Record<string, unknown>, context?: object) => unknown[]} [buildArgs]
97
98
  * @property {string} usage One-line usage string.
98
99
  */
@@ -118,6 +119,7 @@ const COMMANDS = {
118
119
 
119
120
  "branch create": {
120
121
  method: "POST",
122
+ retryUnsafe: true,
121
123
  action: "createBranch",
122
124
  usage: "branch create <branchName> [--issue KEY] [--project KEY]",
123
125
  buildArgs: ([branchName]) => {
@@ -128,6 +130,7 @@ const COMMANDS = {
128
130
  },
129
131
  "branch delete": {
130
132
  method: "POST",
133
+ retryUnsafe: true,
131
134
  action: "deleteBranch",
132
135
  destructive: true,
133
136
  usage: "branch delete <branchName> [--issue KEY] [--project KEY] --force",
@@ -140,6 +143,7 @@ const COMMANDS = {
140
143
 
141
144
  "commit template": {
142
145
  method: "POST",
146
+ retryUnsafe: true,
143
147
  action: "commitByTemplate",
144
148
  usage:
145
149
  'commit template <worktreePath> [--values \'{"key":"val"}\'] [--no-push] [--issue KEY] [--project KEY]' +
@@ -159,6 +163,7 @@ const COMMANDS = {
159
163
 
160
164
  "mr create": {
161
165
  method: "POST",
166
+ retryUnsafe: true,
162
167
  action: "createPullRequest",
163
168
  usage:
164
169
  "mr create <title> <headBranch> [--body <text>] [--base <branch>] [--type <idOrLabel>]" +
@@ -199,6 +204,7 @@ const COMMANDS = {
199
204
  },
200
205
  "mr close": {
201
206
  method: "POST",
207
+ retryUnsafe: true,
202
208
  action: "closePullRequest",
203
209
  destructive: true,
204
210
  usage: "mr close <prId> [--issue KEY] [--project KEY] --force",
@@ -206,6 +212,7 @@ const COMMANDS = {
206
212
  },
207
213
  "mr merge": {
208
214
  method: "POST",
215
+ retryUnsafe: true,
209
216
  action: "mergePullRequest",
210
217
  destructive: true,
211
218
  usage:
@@ -309,6 +316,7 @@ const COMMANDS = {
309
316
  },
310
317
  "pipeline retry": {
311
318
  method: "POST",
319
+ retryUnsafe: true,
312
320
  action: "restartFailedJobs",
313
321
  usage: "pipeline retry <pipelineOrRunId> [--all] [--job <jobId>] [--issue KEY] [--project KEY]",
314
322
  buildArgs: ([id], flags) => {
@@ -329,6 +337,7 @@ const COMMANDS = {
329
337
 
330
338
  "slack notify": {
331
339
  method: "POST",
340
+ retryUnsafe: true,
332
341
  action: "notifySlack",
333
342
  usage:
334
343
  'slack notify <message> [--channel ID] [--values \'{"key":"val"}\'] [--issue KEY] [--project KEY]',