@devflow-one/cli 0.1.11 → 0.1.13
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/README.md +18 -1
- package/package.json +1 -1
- package/src/commands.js +93 -18
package/README.md
CHANGED
|
@@ -156,7 +156,13 @@ devflow where # what this directory resolves to
|
|
|
156
156
|
|
|
157
157
|
# inside a configured repo, routing is inferred — no flags
|
|
158
158
|
devflow mr find # issue key from the branch
|
|
159
|
-
|
|
159
|
+
|
|
160
|
+
# the issue's branch, from the repo's branch template (incl. {parentIssueKey}: a
|
|
161
|
+
# sub-issue may resolve to its parent's branch). Check it out before `commit template`.
|
|
162
|
+
devflow branch resolve ABC-124 # read-only: branch name + whether it exists
|
|
163
|
+
devflow branch create --issue ABC-124 # creates it, or reuses an existing one
|
|
164
|
+
devflow branch create feat/x # a name is only a fallback when no issue key is known
|
|
165
|
+
devflow branch create --issue ABC-124 --from release/2026.4 # start from another remote branch
|
|
160
166
|
|
|
161
167
|
# or route explicitly, overriding whatever the directory implies
|
|
162
168
|
devflow branch create feat/x --project ABC
|
|
@@ -177,6 +183,9 @@ devflow mr types --project ABC # configured types: id, label, def
|
|
|
177
183
|
# ({parentWithChildIssueKeys}/{issueKey}/{issueTitle} template tokens) for a branch without one.
|
|
178
184
|
# {issueTitle} needs the Jira API token set in Settings, else it renders empty.
|
|
179
185
|
devflow mr create "Title" feat/x --issue ABC-123
|
|
186
|
+
devflow mr create "Title" --issue ABC-123 # head branch from the branch template
|
|
187
|
+
# a head branch that exists on the remote is used as is; otherwise the template picks it
|
|
188
|
+
# and a different one is replaced (warning on stderr)
|
|
180
189
|
|
|
181
190
|
# routed by issue key (project key = prefix before the last '-')
|
|
182
191
|
devflow mr find ABC-123
|
|
@@ -186,6 +195,14 @@ devflow pipeline status 42 deadbeef --issue ABC-9
|
|
|
186
195
|
devflow commit template /wt/ABC-123 --values '{"summary":"fix login"}' --project ABC
|
|
187
196
|
devflow commit template /wt/ABC-123 --no-push --project ABC # commit locally, skip push
|
|
188
197
|
|
|
198
|
+
# read a PR/MR's title + description, then edit it: --title/--description replace,
|
|
199
|
+
# --append-description adds text after a `---` rule (skipped if already at the end);
|
|
200
|
+
# --base retargets the PR/MR to an existing remote branch
|
|
201
|
+
devflow mr get 42 --project ABC
|
|
202
|
+
devflow mr update 42 --append-description "## Test plan" --project ABC
|
|
203
|
+
devflow mr update 42 --title "Fix login redirect" --project ABC
|
|
204
|
+
devflow mr update 42 --base release/2026.4 --project ABC
|
|
205
|
+
|
|
189
206
|
# assembled fix-review-comments prompt (read-only; runs no AI, makes no writes)
|
|
190
207
|
devflow mr fix-prompt 42 # issue key inferred from the branch
|
|
191
208
|
|
package/package.json
CHANGED
package/src/commands.js
CHANGED
|
@@ -79,6 +79,37 @@ function toText(flag, raw) {
|
|
|
79
79
|
return text;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** Read a value-taking flag verbatim (no trimming), failing when it was given no value. */
|
|
83
|
+
function toRawText(flag, raw) {
|
|
84
|
+
if (raw === true) throw new UsageError(`"--${flag}" requires a value.`);
|
|
85
|
+
|
|
86
|
+
return raw === undefined ? undefined : String(raw);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The issue key whose branch template names `branch`: --issue, else — only when no
|
|
91
|
+
* branch was named — the inferred key, so an unrelated checkout never overrides it.
|
|
92
|
+
*/
|
|
93
|
+
function templateIssueKey(branch, flags, context) {
|
|
94
|
+
return toText("issue", flags.issue) ?? (branch ? undefined : context.issueKey);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** buildArgs for a command whose only arg is an issue key: positional, else --issue / inferred. */
|
|
98
|
+
function issueKeyArgs(name) {
|
|
99
|
+
return ([issueKey], _flags, context = {}) => {
|
|
100
|
+
const key = issueKey ?? context.issueKey;
|
|
101
|
+
|
|
102
|
+
if (!key) {
|
|
103
|
+
throw new UsageError(
|
|
104
|
+
`${name} needs an issue key: pass it positionally or with --issue ` +
|
|
105
|
+
"(none could be inferred from the current branch or directory).",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return [key];
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
82
113
|
/** A bad-invocation error (wrong/missing args) — printed with the command usage. */
|
|
83
114
|
export class UsageError extends Error {
|
|
84
115
|
constructor(message) {
|
|
@@ -121,13 +152,30 @@ const COMMANDS = {
|
|
|
121
152
|
method: "POST",
|
|
122
153
|
retryUnsafe: true,
|
|
123
154
|
action: "createBranch",
|
|
124
|
-
usage:
|
|
125
|
-
|
|
126
|
-
|
|
155
|
+
usage:
|
|
156
|
+
"branch create [branchName] [--issue KEY] [--from <branch>] [--project KEY]" +
|
|
157
|
+
" (the repo's branch template names the branch from the issue, incl. {parentIssueKey};" +
|
|
158
|
+
" branchName is only a fallback; an existing branch is reused; --from picks the remote" +
|
|
159
|
+
" branch to start from instead of the repo's configured base branch)",
|
|
160
|
+
buildArgs: ([branchName], flags, context = {}) => {
|
|
161
|
+
const issueKey = templateIssueKey(branchName, flags, context);
|
|
162
|
+
const from = toBranch("from", flags.from);
|
|
163
|
+
|
|
164
|
+
if (!branchName && !issueKey) {
|
|
165
|
+
throw new UsageError("branch create needs an issue key (--issue) or a <branchName>.");
|
|
166
|
+
}
|
|
127
167
|
|
|
128
|
-
return [branchName];
|
|
168
|
+
return from === undefined ? [branchName, issueKey] : [branchName, issueKey, from];
|
|
129
169
|
},
|
|
130
170
|
},
|
|
171
|
+
"branch resolve": {
|
|
172
|
+
method: "POST",
|
|
173
|
+
action: "resolveBranchName",
|
|
174
|
+
usage:
|
|
175
|
+
"branch resolve [issueKey] [--issue KEY] [--project KEY]" +
|
|
176
|
+
" (prints the issue's configured branch name and whether it exists; read-only)",
|
|
177
|
+
buildArgs: issueKeyArgs("branch resolve"),
|
|
178
|
+
},
|
|
131
179
|
"branch delete": {
|
|
132
180
|
method: "POST",
|
|
133
181
|
retryUnsafe: true,
|
|
@@ -150,7 +198,8 @@ const COMMANDS = {
|
|
|
150
198
|
" (--values keys fill the project's commit template, default" +
|
|
151
199
|
' "{summary}\\n\\n{changesList}\\n\\n{issueSlug}": summary, changesList (string array,' +
|
|
152
200
|
" rendered as bullets), issueSlug, prNumber; missing keys are left empty and reported" +
|
|
153
|
-
" as warnings on stderr
|
|
201
|
+
" as warnings on stderr; commits the worktree's checked-out branch, so check out the" +
|
|
202
|
+
" one `branch resolve` prints first)",
|
|
154
203
|
buildArgs: ([worktreePath], flags) => {
|
|
155
204
|
if (!worktreePath) throw new UsageError("commit template requires <worktreePath>.");
|
|
156
205
|
|
|
@@ -166,20 +215,23 @@ const COMMANDS = {
|
|
|
166
215
|
retryUnsafe: true,
|
|
167
216
|
action: "createPullRequest",
|
|
168
217
|
usage:
|
|
169
|
-
"mr create <title>
|
|
218
|
+
"mr create <title> [headBranch] [--body <text>] [--base <branch>] [--type <idOrLabel>]" +
|
|
170
219
|
" [--issue KEY] [--project KEY]" +
|
|
171
|
-
" (
|
|
220
|
+
" (a headBranch that exists on the remote is used as is; otherwise the repo's branch" +
|
|
221
|
+
" template picks the head branch from the issue; --base defaults to the repo's configured base branch, expanding a {year}/{number} pattern;" +
|
|
172
222
|
" --type is auto-selected by matching PR type names against the title prefix, branch prefix," +
|
|
173
223
|
" Jira issue type and title keywords, falling back to the first configured type)",
|
|
174
|
-
buildArgs: ([title, headBranch], flags) => {
|
|
175
|
-
|
|
176
|
-
|
|
224
|
+
buildArgs: ([title, headBranch], flags, context = {}) => {
|
|
225
|
+
const issueKey = templateIssueKey(headBranch, flags, context);
|
|
226
|
+
|
|
227
|
+
if (!title || (!headBranch && !issueKey)) {
|
|
228
|
+
throw new UsageError(
|
|
229
|
+
"mr create requires <title> and an issue key (--issue) or <headBranch>.",
|
|
230
|
+
);
|
|
177
231
|
}
|
|
178
232
|
|
|
179
233
|
const baseBranch = toBranch("base", flags.base);
|
|
180
234
|
const prType = toText("type", flags.type);
|
|
181
|
-
// Only an explicit --issue is forwarded; the gateway otherwise parses the head branch.
|
|
182
|
-
const issueKey = toText("issue", flags.issue);
|
|
183
235
|
|
|
184
236
|
// Trailing undefined is trimmed so the gateway resolves the repo's own
|
|
185
237
|
// configured base branch (and expands a pattern) instead of seeing null.
|
|
@@ -265,17 +317,40 @@ const COMMANDS = {
|
|
|
265
317
|
usage: "mr find [issueKey] [--issue KEY] [--project KEY]",
|
|
266
318
|
// The positional wins, then --issue, then the issue key inferred from the
|
|
267
319
|
// branch — so `devflow mr find` on `ABC-123-login` needs no argument at all.
|
|
268
|
-
buildArgs: (
|
|
269
|
-
|
|
320
|
+
buildArgs: issueKeyArgs("mr find"),
|
|
321
|
+
},
|
|
322
|
+
"mr get": {
|
|
323
|
+
method: "POST",
|
|
324
|
+
action: "getPullRequest",
|
|
325
|
+
usage: "mr get <prId> [--issue KEY] [--project KEY] (title, description, state; read-only)",
|
|
326
|
+
buildArgs: ([prId]) => [toInt("prId", prId)],
|
|
327
|
+
},
|
|
328
|
+
"mr update": {
|
|
329
|
+
method: "POST",
|
|
330
|
+
action: "updatePullRequest",
|
|
331
|
+
usage:
|
|
332
|
+
"mr update <prId> [--title <text>] [--description <text> | --append-description <text>]" +
|
|
333
|
+
" [--base <branch>] [--issue KEY] [--project KEY]" +
|
|
334
|
+
" (--title/--description replace; --append-description adds text after a `---` rule" +
|
|
335
|
+
" and is skipped if it already ends the description after that rule; --base retargets" +
|
|
336
|
+
" the PR/MR to an existing remote branch)",
|
|
337
|
+
buildArgs: ([prId], flags) => {
|
|
338
|
+
const title = toText("title", flags.title);
|
|
339
|
+
const body = toRawText("description", flags.description);
|
|
340
|
+
const appendBody = toRawText("append-description", flags["append-description"]);
|
|
341
|
+
const baseBranch = toBranch("base", flags.base);
|
|
270
342
|
|
|
271
|
-
if (
|
|
343
|
+
if ([title, body, appendBody, baseBranch].every((value) => value === undefined)) {
|
|
272
344
|
throw new UsageError(
|
|
273
|
-
"mr
|
|
274
|
-
"(none could be inferred from the current branch or directory).",
|
|
345
|
+
"mr update needs --title, --description, --append-description or --base.",
|
|
275
346
|
);
|
|
276
347
|
}
|
|
277
348
|
|
|
278
|
-
|
|
349
|
+
if (body !== undefined && appendBody !== undefined) {
|
|
350
|
+
throw new UsageError("mr update takes --description or --append-description, not both.");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return [toInt("prId", prId), { title, body, appendBody, baseBranch }];
|
|
279
354
|
},
|
|
280
355
|
},
|
|
281
356
|
"mr types": {
|