@navbook/cli 0.4.0 → 0.5.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/dist/args.d.ts +28 -0
- package/dist/args.js +48 -0
- package/dist/args.js.map +1 -0
- package/dist/commands/complete.d.ts +10 -0
- package/dist/commands/complete.js +59 -51
- package/dist/commands/complete.js.map +1 -1
- package/dist/commands/compose.d.ts +35 -0
- package/dist/commands/doctor.d.ts +15 -0
- package/dist/commands/entity.d.ts +73 -0
- package/dist/commands/entity.js +59 -44
- package/dist/commands/entity.js.map +1 -1
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/install.d.ts +17 -0
- package/dist/commands/install.js +6 -3
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/issue.d.ts +35 -0
- package/dist/commands/issue.js +4 -5
- package/dist/commands/issue.js.map +1 -1
- package/dist/commands/plugin.d.ts +36 -0
- package/dist/commands/plugin.js +386 -0
- package/dist/commands/plugin.js.map +1 -0
- package/dist/commands/policy.d.ts +58 -0
- package/dist/commands/policy.js +13 -0
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/pr-elsewhere.d.ts +40 -0
- package/dist/commands/pr-elsewhere.js +209 -0
- package/dist/commands/pr-elsewhere.js.map +1 -0
- package/dist/commands/pr.d.ts +69 -0
- package/dist/commands/pr.js +86 -60
- package/dist/commands/pr.js.map +1 -1
- package/dist/context.d.ts +42 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -1
- package/dist/editor.d.ts +23 -0
- package/dist/editor.js +4 -0
- package/dist/editor.js.map +1 -1
- package/dist/errors.d.ts +17 -0
- package/dist/install/completions.d.ts +19 -0
- package/dist/install/hook.d.ts +32 -0
- package/dist/install/hook.js +48 -4
- package/dist/install/hook.js.map +1 -1
- package/dist/main.d.ts +21 -0
- package/dist/main.js +112 -8
- package/dist/main.js.map +1 -1
- package/dist/plugins/commands.d.ts +69 -0
- package/dist/plugins/commands.js +148 -0
- package/dist/plugins/commands.js.map +1 -0
- package/dist/plugins/hint.d.ts +23 -0
- package/dist/plugins/hint.js +65 -0
- package/dist/plugins/hint.js.map +1 -0
- package/dist/plugins/host.d.ts +125 -0
- package/dist/plugins/host.js +18 -0
- package/dist/plugins/host.js.map +1 -0
- package/dist/plugins/loader.d.ts +54 -0
- package/dist/plugins/loader.js +165 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/resolve.d.ts +50 -0
- package/dist/plugins/resolve.js +134 -0
- package/dist/plugins/resolve.js.map +1 -0
- package/dist/plugins/runtime.d.ts +69 -0
- package/dist/plugins/runtime.js +256 -0
- package/dist/plugins/runtime.js.map +1 -0
- package/dist/plugins/store.d.ts +96 -0
- package/dist/plugins/store.js +186 -0
- package/dist/plugins/store.js.map +1 -0
- package/dist/program.d.ts +49 -0
- package/dist/program.js +351 -113
- package/dist/program.js.map +1 -1
- package/dist/prompt.d.ts +31 -0
- package/dist/render/colors.d.ts +13 -0
- package/dist/render/detail.d.ts +22 -0
- package/dist/render/table.d.ts +32 -0
- package/dist/render/table.js +123 -13
- package/dist/render/table.js.map +1 -1
- package/dist/sort.d.ts +27 -0
- package/package.json +9 -2
- package/dist/commands/feature.js +0 -202
- package/dist/commands/feature.js.map +0 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saying what the review policy counts, and what it is short of.
|
|
3
|
+
*
|
|
4
|
+
* Shared by every verb that reads the policy, so a pull request short of two
|
|
5
|
+
* approvals is described in the same words wherever it is mentioned. Nothing
|
|
6
|
+
* here refuses anything: spec 02 §2.7 forbids acting on a review state, and
|
|
7
|
+
* §2.10 keeps a declared policy inside that. These are sentences, not gates.
|
|
8
|
+
*/
|
|
9
|
+
import { type MarkerVersionFault, type MergePolicyReading, type ReviewPolicyReading, type ReviewSummary } from "@navbook/core";
|
|
10
|
+
import type { Ctx } from "../context.ts";
|
|
11
|
+
/** Warn about a marker whose policy could not be read; D15 is the full report. */
|
|
12
|
+
export declare function warnPolicyProblems(ctx: Ctx, reading: ReviewPolicyReading): void;
|
|
13
|
+
/**
|
|
14
|
+
* The same, for the merge policy (spec 02 §2.10).
|
|
15
|
+
*
|
|
16
|
+
* Kept separate from the review policy's rather than folded into it, because a
|
|
17
|
+
* merge is the one command that reads both and a caller reading only one
|
|
18
|
+
* should not be made to hold the other.
|
|
19
|
+
*/
|
|
20
|
+
export declare function warnMergePolicyProblems(ctx: Ctx, reading: MergePolicyReading): void;
|
|
21
|
+
/**
|
|
22
|
+
* Say that the tree was written by a newer Navbook than this one (D16).
|
|
23
|
+
*
|
|
24
|
+
* Only the newer kind: a malformed `version` changes nothing a command does
|
|
25
|
+
* and is D15's to report, while a newer one means every answer that follows
|
|
26
|
+
* is this tool's guess at a format it has never seen — which is worth hearing
|
|
27
|
+
* before the answer rather than after a confusing one.
|
|
28
|
+
*/
|
|
29
|
+
export declare function warnNewerFormat(ctx: Ctx, fault: MarkerVersionFault | null): void;
|
|
30
|
+
/**
|
|
31
|
+
* What stands between a pull request and its policy, or null when nothing does.
|
|
32
|
+
*
|
|
33
|
+
* Only asked of a repository that declared a policy: a decision of `pending`
|
|
34
|
+
* where nobody asked for anything is not a shortfall, and saying so on every
|
|
35
|
+
* merge would be noise about a project that never opted in.
|
|
36
|
+
*/
|
|
37
|
+
export declare function describeShortfall(summary: ReviewSummary): string | null;
|
|
38
|
+
/** What a merge should do about a shortfall, before anything is written. */
|
|
39
|
+
export type MergeAction = "proceed" | "warn-and-proceed" | "ask";
|
|
40
|
+
export interface MergeSituation {
|
|
41
|
+
/** What the pull request is short of, or null when it is short of nothing. */
|
|
42
|
+
shortfall: string | null;
|
|
43
|
+
/** `--yes` was given. */
|
|
44
|
+
assumeYes: boolean;
|
|
45
|
+
/** There is somebody at a terminal to answer a question. */
|
|
46
|
+
interactive: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Decide how a merge should treat an unmet policy.
|
|
50
|
+
*
|
|
51
|
+
* Kept apart from the merge itself so every combination can be checked without
|
|
52
|
+
* a repository or a terminal, and so the one rule that matters is visible in
|
|
53
|
+
* one place: no combination returns a refusal. `--yes` proceeds, a terminal is
|
|
54
|
+
* asked, and everything else says its piece and proceeds — because a pipeline
|
|
55
|
+
* that stopped for a question nobody can answer would be the gate spec 02 §2.7
|
|
56
|
+
* forbids, arrived at by accident rather than on purpose.
|
|
57
|
+
*/
|
|
58
|
+
export declare function mergeAction(situation: MergeSituation): MergeAction;
|
package/dist/commands/policy.js
CHANGED
|
@@ -26,6 +26,19 @@ function warnMarkerProblems(ctx, problems) {
|
|
|
26
26
|
ctx.stderr.write(`${ctx.colors.yellow("warning:")} ${ctx.navDir}/${NAV_MARKER}: ${problem}; using the default\n`);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Say that the tree was written by a newer Navbook than this one (D16).
|
|
31
|
+
*
|
|
32
|
+
* Only the newer kind: a malformed `version` changes nothing a command does
|
|
33
|
+
* and is D15's to report, while a newer one means every answer that follows
|
|
34
|
+
* is this tool's guess at a format it has never seen — which is worth hearing
|
|
35
|
+
* before the answer rather than after a confusing one.
|
|
36
|
+
*/
|
|
37
|
+
export function warnNewerFormat(ctx, fault) {
|
|
38
|
+
if (fault?.kind !== "newer")
|
|
39
|
+
return;
|
|
40
|
+
ctx.stderr.write(`${ctx.colors.yellow("warning:")} ${ctx.navDir}/${NAV_MARKER}: ${fault.message}\n`);
|
|
41
|
+
}
|
|
29
42
|
/**
|
|
30
43
|
* What stands between a pull request and its policy, or null when nothing does.
|
|
31
44
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/commands/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/commands/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAGL,UAAU,GAGX,MAAM,eAAe,CAAC;AAGvB,kFAAkF;AAClF,MAAM,UAAU,kBAAkB,CAAC,GAAQ,EAAE,OAA4B;IACvE,kBAAkB,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAQ,EAAE,OAA2B;IAC3E,kBAAkB,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAQ,EAAE,QAA2B;IAC/D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,GAAG,CAAC,MAAM,CAAC,KAAK,CACd,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,UAAU,KAAK,OAAO,uBAAuB,CAChG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,GAAQ,EAAE,KAAgC;IACxE,IAAI,KAAK,EAAE,IAAI,KAAK,OAAO;QAAE,OAAO;IACpC,GAAG,CAAC,MAAM,CAAC,KAAK,CACd,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,UAAU,KAAK,KAAK,CAAC,OAAO,IAAI,CACnF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAsB;IACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS;SAC/B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,iBAAiB,CAAC;SACpD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,wBAAwB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAE9E,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IAC9C,IAAI,KAAK,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,GAAG,KAAK,OAAO,QAAQ,qBAAqB,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AACjF,CAAC;AAcD;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,SAAyB;IACnD,IAAI,SAAS,CAAC,SAAS,KAAK,IAAI,IAAI,SAAS,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1E,OAAO,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,kBAAkB,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writing to a pull request whose branch is not checked out here.
|
|
3
|
+
*
|
|
4
|
+
* A pull request's files live on its source branch (spec 03 §3.5), so the
|
|
5
|
+
* verbs that write into its directory refuse from any other checkout — core's
|
|
6
|
+
* `findPrToWrite` names the branch, and the worktree that has it. Everything
|
|
7
|
+
* needed to perform the write anyway is already known at that moment, so when
|
|
8
|
+
* somebody can be asked, this asks instead of only pointing:
|
|
9
|
+
*
|
|
10
|
+
* - When a clean worktree already has the branch, the write happens there.
|
|
11
|
+
* - When none has it, the branch is checked out into a temporary worktree,
|
|
12
|
+
* the write happens there, and the worktree goes away again once it holds
|
|
13
|
+
* nothing the user would lose.
|
|
14
|
+
*
|
|
15
|
+
* No directory changes either way. A child process cannot move its parent
|
|
16
|
+
* shell's working directory, and nothing is spawned in any case: a second
|
|
17
|
+
* `Ctx` rooted at the other worktree is enough, and the verb runs against it
|
|
18
|
+
* unchanged.
|
|
19
|
+
*/
|
|
20
|
+
import { type EntityRecord } from "@navbook/core";
|
|
21
|
+
import type { Ctx } from "../context.ts";
|
|
22
|
+
/**
|
|
23
|
+
* Help for `-y`, which answers the question in advance. The same letter
|
|
24
|
+
* answers `nav pr merge` and `nav install` in advance, and means the same
|
|
25
|
+
* thing here: this verb's one question, whichever of the two it would be.
|
|
26
|
+
*/
|
|
27
|
+
export declare const YES_HELP = "accept the offer to write in a worktree on the source branch \u2014 the clean one that has it, or a temporary one";
|
|
28
|
+
export interface PrWriteOptions {
|
|
29
|
+
/** `-y`: accept either offer in advance, and ask nobody. */
|
|
30
|
+
yes?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Run a pull-request write where it belongs, asking first when that is not here.
|
|
34
|
+
*
|
|
35
|
+
* `write` gets the context to write in and the pull request as that context
|
|
36
|
+
* sees it. When the write cannot or may not move, this is core's refusal
|
|
37
|
+
* naming where the pull request lives — declining the question included: the
|
|
38
|
+
* answer was "not there", not "do it here".
|
|
39
|
+
*/
|
|
40
|
+
export declare function withPrWriteSite<T>(ctx: Ctx, prefix: string, opts: PrWriteOptions, write: (at: Ctx, entity: EntityRecord) => T): T;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writing to a pull request whose branch is not checked out here.
|
|
3
|
+
*
|
|
4
|
+
* A pull request's files live on its source branch (spec 03 §3.5), so the
|
|
5
|
+
* verbs that write into its directory refuse from any other checkout — core's
|
|
6
|
+
* `findPrToWrite` names the branch, and the worktree that has it. Everything
|
|
7
|
+
* needed to perform the write anyway is already known at that moment, so when
|
|
8
|
+
* somebody can be asked, this asks instead of only pointing:
|
|
9
|
+
*
|
|
10
|
+
* - When a clean worktree already has the branch, the write happens there.
|
|
11
|
+
* - When none has it, the branch is checked out into a temporary worktree,
|
|
12
|
+
* the write happens there, and the worktree goes away again once it holds
|
|
13
|
+
* nothing the user would lose.
|
|
14
|
+
*
|
|
15
|
+
* No directory changes either way. A child process cannot move its parent
|
|
16
|
+
* shell's working directory, and nothing is spawned in any case: a second
|
|
17
|
+
* `Ctx` rooted at the other worktree is enough, and the verb runs against it
|
|
18
|
+
* unchanged.
|
|
19
|
+
*/
|
|
20
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { addWorktree, findPrToWrite, gitRun, isTreeClean, locatePrToWrite, refusePrWrite, removeWorktree, } from "@navbook/core";
|
|
24
|
+
import { makeContext } from "../context.js";
|
|
25
|
+
import { fail } from "../errors.js";
|
|
26
|
+
import { askYesNo, isInteractive } from "../prompt.js";
|
|
27
|
+
/**
|
|
28
|
+
* Help for `-y`, which answers the question in advance. The same letter
|
|
29
|
+
* answers `nav pr merge` and `nav install` in advance, and means the same
|
|
30
|
+
* thing here: this verb's one question, whichever of the two it would be.
|
|
31
|
+
*/
|
|
32
|
+
export const YES_HELP = "accept the offer to write in a worktree on the source branch — the clean one that has it, or a temporary one";
|
|
33
|
+
/**
|
|
34
|
+
* Where the write could go instead of here, or null when nowhere can take it.
|
|
35
|
+
*
|
|
36
|
+
* An existing worktree must be clean: without `--commit` a write leaves a
|
|
37
|
+
* staged change behind, and one mixed into somebody's work in progress where
|
|
38
|
+
* they are not looking is not a favour; with it, the commit would be refused
|
|
39
|
+
* over the unrelated staged paths anyway. A branch no worktree has can be
|
|
40
|
+
* checked out into a new one — but only a local branch: checking out a
|
|
41
|
+
* remote-tracking one would create a local branch as a side effect, which is
|
|
42
|
+
* more than a comment should do.
|
|
43
|
+
*/
|
|
44
|
+
function destination(where) {
|
|
45
|
+
if (where.sourceRemote)
|
|
46
|
+
return null;
|
|
47
|
+
if (where.worktree === null)
|
|
48
|
+
return { branch: where.branch, worktree: null };
|
|
49
|
+
return where.worktreeClean ? { branch: where.branch, worktree: where.worktree } : null;
|
|
50
|
+
}
|
|
51
|
+
/** Ask whether to write there, unless `-y` already said yes. */
|
|
52
|
+
function agreed(ctx, entity, to, opts) {
|
|
53
|
+
if (opts.yes)
|
|
54
|
+
return true;
|
|
55
|
+
// Only ever asked, never assumed. A run with nothing to answer the question
|
|
56
|
+
// keeps the refusal it has always had: a pipeline that started writing into
|
|
57
|
+
// a checkout nobody mentioned would be a worse surprise than exit 1.
|
|
58
|
+
if (!isInteractive(ctx))
|
|
59
|
+
return false;
|
|
60
|
+
if (to.worktree !== null) {
|
|
61
|
+
ctx.stdout.write(`#${entity.id} is on '${to.branch}', checked out in ${to.worktree} ${ctx.colors.dim("(clean)")}\n`);
|
|
62
|
+
return askYesNo(ctx, "Write it there? [y/N] ");
|
|
63
|
+
}
|
|
64
|
+
ctx.stdout.write(`#${entity.id} is on '${to.branch}', which no worktree has checked out\n`);
|
|
65
|
+
return askYesNo(ctx, "Check it out in a temporary worktree and write it there? [y/N] ");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A context rooted at `worktree`, reporting to the terminal we already have.
|
|
69
|
+
*
|
|
70
|
+
* The Navbook directory is the one already resolved here rather than found
|
|
71
|
+
* again: the pull request was located under it, and a second discovery from
|
|
72
|
+
* another root is one more chance to land somewhere it is not. The plugin
|
|
73
|
+
* extensions carry over for the same reason: the other checkout's tree is
|
|
74
|
+
* the same format, and read without them its registered keys would be raw.
|
|
75
|
+
*/
|
|
76
|
+
function contextIn(ctx, worktree) {
|
|
77
|
+
return makeContext({
|
|
78
|
+
cwd: worktree,
|
|
79
|
+
env: ctx.env,
|
|
80
|
+
stdout: ctx.stdout,
|
|
81
|
+
stderr: ctx.stderr,
|
|
82
|
+
navDir: ctx.navDir,
|
|
83
|
+
ext: ctx.ext,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Run `write` in `worktree`, refusing if that checkout does not hold the pull
|
|
88
|
+
* request after all — the branch moved since we looked, say. Without this the
|
|
89
|
+
* write would land beside no `pr.md`, the stranded comment the refusal exists
|
|
90
|
+
* to prevent.
|
|
91
|
+
*/
|
|
92
|
+
function writeIn(ctx, worktree, prefix, write) {
|
|
93
|
+
const there = contextIn(ctx, worktree);
|
|
94
|
+
return write(there, findPrToWrite(there, prefix));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A new worktree on `branch` in a fresh temporary directory.
|
|
98
|
+
*
|
|
99
|
+
* `mkdtemp` rather than a fixed name, so two runs never collide and nothing a
|
|
100
|
+
* user made is ever in the way; under `os.tmpdir()`, so `TMPDIR` says where.
|
|
101
|
+
* The branch only flavours the name, so it is cut short: a directory name has
|
|
102
|
+
* a length limit and a branch name has none.
|
|
103
|
+
*/
|
|
104
|
+
function checkOutTemporarily(ctx, branch) {
|
|
105
|
+
const safe = branch.replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 40);
|
|
106
|
+
let dir = null;
|
|
107
|
+
try {
|
|
108
|
+
dir = mkdtempSync(join(tmpdir(), `nav-${safe}-`));
|
|
109
|
+
addWorktree(ctx.repoRoot, dir, branch);
|
|
110
|
+
return dir;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (dir !== null)
|
|
114
|
+
rmSync(dir, { recursive: true, force: true });
|
|
115
|
+
fail(`could not check '${branch}' out into a temporary worktree`, [
|
|
116
|
+
error instanceof Error ? error.message.trim() : String(error),
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Take a temporary worktree away again, unless it holds what the user would lose.
|
|
122
|
+
*
|
|
123
|
+
* After `--commit` the write is on the branch and the worktree holds nothing.
|
|
124
|
+
* Without it — or when the commit itself failed, a hook refusing it, say — the
|
|
125
|
+
* write is staged there and nowhere else, so the worktree stays and says where
|
|
126
|
+
* it is: removing it would throw away a review somebody just wrote. A run that
|
|
127
|
+
* failed before writing anything leaves it clean, and it goes. Untracked files
|
|
128
|
+
* do not count: nothing but this run has been in there.
|
|
129
|
+
*/
|
|
130
|
+
function release(ctx, dir, branch, failed) {
|
|
131
|
+
if (isTreeClean(dir, { untracked: false })) {
|
|
132
|
+
try {
|
|
133
|
+
removeWorktree(ctx.repoRoot, dir, { force: true });
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Never mask the error that brought us here with one about tidying up.
|
|
137
|
+
// Deleting the directory alone would leave git recording the branch as
|
|
138
|
+
// checked out at a path that no longer exists, so prune that too.
|
|
139
|
+
rmSync(dir, { recursive: true, force: true });
|
|
140
|
+
gitRun(["worktree", "prune"], { cwd: ctx.repoRoot });
|
|
141
|
+
}
|
|
142
|
+
if (!failed) {
|
|
143
|
+
ctx.stderr.write(`${ctx.colors.dim(`written in a temporary worktree on '${branch}', since removed`)}\n`);
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
ctx.stderr.write(`${ctx.colors.yellow("note:")} the change is staged in ${dir}, a temporary worktree on '${branch}'\n` +
|
|
148
|
+
`commit it there, then remove it: 'git worktree remove ${dir}'\n`);
|
|
149
|
+
}
|
|
150
|
+
/** Terminal signals, which reach the editor and git as well as us. */
|
|
151
|
+
const TERMINAL_SIGNALS = ["SIGINT", "SIGQUIT", "SIGHUP"];
|
|
152
|
+
/**
|
|
153
|
+
* Run `body` with terminal signals left to the children.
|
|
154
|
+
*
|
|
155
|
+
* Ctrl-C while `$EDITOR` is open goes to the whole foreground process group.
|
|
156
|
+
* By default it would kill us mid-`spawnSync`, before any `finally` runs, and
|
|
157
|
+
* leave the temporary worktree registered with the branch checked out. Git
|
|
158
|
+
* ignores these signals while its own editor runs, for the same reason; so
|
|
159
|
+
* does this. The editor still gets the signal, exits, and the command fails
|
|
160
|
+
* through its ordinary path — which is the one that tidies up.
|
|
161
|
+
*/
|
|
162
|
+
function sheltered(body) {
|
|
163
|
+
const ignore = () => { };
|
|
164
|
+
for (const signal of TERMINAL_SIGNALS)
|
|
165
|
+
process.on(signal, ignore);
|
|
166
|
+
try {
|
|
167
|
+
return body();
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
for (const signal of TERMINAL_SIGNALS)
|
|
171
|
+
process.off(signal, ignore);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Run a pull-request write where it belongs, asking first when that is not here.
|
|
176
|
+
*
|
|
177
|
+
* `write` gets the context to write in and the pull request as that context
|
|
178
|
+
* sees it. When the write cannot or may not move, this is core's refusal
|
|
179
|
+
* naming where the pull request lives — declining the question included: the
|
|
180
|
+
* answer was "not there", not "do it here".
|
|
181
|
+
*/
|
|
182
|
+
export function withPrWriteSite(ctx, prefix, opts, write) {
|
|
183
|
+
const { entity, elsewhere } = locatePrToWrite(ctx, prefix);
|
|
184
|
+
if (elsewhere === null)
|
|
185
|
+
return write(ctx, entity);
|
|
186
|
+
const to = destination(elsewhere);
|
|
187
|
+
// Built from what was just found, rather than scanning every ref again.
|
|
188
|
+
if (to === null || !agreed(ctx, entity, to, opts))
|
|
189
|
+
refusePrWrite(entity, elsewhere);
|
|
190
|
+
if (to.worktree !== null) {
|
|
191
|
+
const result = writeIn(ctx, to.worktree, prefix, write);
|
|
192
|
+
ctx.stderr.write(`${ctx.colors.dim(`written in ${to.worktree}`)}\n`);
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
const { branch } = to;
|
|
196
|
+
return sheltered(() => {
|
|
197
|
+
const dir = checkOutTemporarily(ctx, branch);
|
|
198
|
+
let failed = true;
|
|
199
|
+
try {
|
|
200
|
+
const result = writeIn(ctx, dir, prefix, write);
|
|
201
|
+
failed = false;
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
release(ctx, dir, branch, failed);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=pr-elsewhere.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-elsewhere.js","sourceRoot":"","sources":["../../src/commands/pr-elsewhere.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nav pr <verb>` — spec 04 §4.3.
|
|
3
|
+
*
|
|
4
|
+
* The eight shared verbs come from `entity.ts`; this module adds the four that
|
|
5
|
+
* only pull requests have (`update`, `request`, `review`, `merge`) and the
|
|
6
|
+
* parts of the shared verbs that must reach across branches, because a PR's
|
|
7
|
+
* files live on the branch it proposes to merge (spec 03 §3.5).
|
|
8
|
+
*/
|
|
9
|
+
import type { Ctx } from "../context.ts";
|
|
10
|
+
import { type CloseOptions, type GlobalFlags, type ListOptions } from "./entity.ts";
|
|
11
|
+
import { type PrWriteOptions } from "./pr-elsewhere.ts";
|
|
12
|
+
export interface PrOpenOptions extends GlobalFlags {
|
|
13
|
+
target?: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
message?: string;
|
|
16
|
+
draft?: boolean;
|
|
17
|
+
label?: string[];
|
|
18
|
+
assignee?: string[];
|
|
19
|
+
reviewer?: string[];
|
|
20
|
+
milestone?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Frontmatter a plugin contributed, from an option it declared (§2.12).
|
|
23
|
+
*
|
|
24
|
+
* Written by the same composer that writes the format's own keys, so a
|
|
25
|
+
* plugin's value lands in the file exactly as a built-in one would.
|
|
26
|
+
*/
|
|
27
|
+
ext?: Record<string, string | readonly string[]>;
|
|
28
|
+
}
|
|
29
|
+
export declare function cmdPrOpen(ctx: Ctx, opts: PrOpenOptions): void;
|
|
30
|
+
export interface PrUpdateOptions extends GlobalFlags, PrWriteOptions {
|
|
31
|
+
}
|
|
32
|
+
export declare function cmdPrUpdate(ctx: Ctx, prefix: string, opts: PrUpdateOptions): void;
|
|
33
|
+
export interface RequestOptions extends GlobalFlags, PrWriteOptions {
|
|
34
|
+
remove?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function cmdPrRequest(ctx: Ctx, prefix: string, people: string[], opts: RequestOptions): void;
|
|
37
|
+
export interface ReviewOptions extends GlobalFlags, PrWriteOptions {
|
|
38
|
+
approve?: boolean;
|
|
39
|
+
requestChanges?: boolean;
|
|
40
|
+
comment?: boolean;
|
|
41
|
+
message?: string;
|
|
42
|
+
revision?: string;
|
|
43
|
+
file?: string;
|
|
44
|
+
line?: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function cmdPrReview(ctx: Ctx, prefix: string, opts: ReviewOptions): void;
|
|
47
|
+
export interface PrListOptions extends ListOptions {
|
|
48
|
+
allRefs?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export declare function cmdPrList(ctx: Ctx, terms: string[], opts: PrListOptions): void;
|
|
51
|
+
export interface MergeOptions extends GlobalFlags {
|
|
52
|
+
/** `--method`: land by this method instead of the marker's (spec 02 §2.10). */
|
|
53
|
+
method?: string;
|
|
54
|
+
continue?: boolean;
|
|
55
|
+
/** Answer the review-policy question in advance. */
|
|
56
|
+
yes?: boolean;
|
|
57
|
+
/** `--no-sync-source`: leave the source branch where it is. */
|
|
58
|
+
syncSource?: boolean;
|
|
59
|
+
}
|
|
60
|
+
export declare function cmdPrMerge(ctx: Ctx, prefix: string | undefined, opts: MergeOptions): void;
|
|
61
|
+
/**
|
|
62
|
+
* Decline a pull request.
|
|
63
|
+
*
|
|
64
|
+
* Its files usually live on the source branch, so when the ID is not in the
|
|
65
|
+
* current tree the directory is first checked out from the ref that has it —
|
|
66
|
+
* which is exactly the manual recipe spec 02 §2.8 describes for keeping a
|
|
67
|
+
* durable record of a declined PR on the default branch.
|
|
68
|
+
*/
|
|
69
|
+
export declare function cmdPrClose(ctx: Ctx, prefix: string, opts: CloseOptions): void;
|
package/dist/commands/pr.js
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
* parts of the shared verbs that must reach across branches, because a PR's
|
|
7
7
|
* files live on the branch it proposes to merge (spec 03 §3.5).
|
|
8
8
|
*/
|
|
9
|
-
import { applyComment, bindReviewRevision, commitReport, continuePrMerge, countOpenPrsOnOtherRefs, currentAuthor, entityJson, executePrMerge,
|
|
9
|
+
import { applyComment, bindReviewRevision, commitReport, continuePrMerge, countOpenPrsOnOtherRefs, currentAuthor, entityJson, executePrMerge, isMergeMethod, listEntities, listPrsAcrossRefs, MERGE_METHODS, materializePrIfAbsent, newCommentFile, newPrFile, openPr, parseListQuery, parsePerson, planPrMerge, preparePrOpen, readReviewers, readReviewPolicy, requestReview, reviewSummary, sameEmail, stringField, toNdjson, updatePr, validateComment, validatePr, } from "@navbook/core";
|
|
10
10
|
import { fail } from "../errors.js";
|
|
11
11
|
import { askYesNo, isInteractive } from "../prompt.js";
|
|
12
12
|
import { composeFile } from "./compose.js";
|
|
13
13
|
import { cmdClose, reportList, } from "./entity.js";
|
|
14
14
|
import { describeShortfall, mergeAction, warnMergePolicyProblems, warnPolicyProblems, } from "./policy.js";
|
|
15
|
+
import { withPrWriteSite } from "./pr-elsewhere.js";
|
|
15
16
|
export function cmdPrOpen(ctx, opts) {
|
|
16
17
|
const draft = preparePrOpen(ctx, { target: opts.target, title: opts.title });
|
|
17
18
|
const composed = composeFile(ctx, {
|
|
@@ -31,9 +32,9 @@ export function cmdPrOpen(ctx, opts) {
|
|
|
31
32
|
labels: opts.label,
|
|
32
33
|
assignee: opts.assignee,
|
|
33
34
|
milestone: opts.milestone,
|
|
34
|
-
|
|
35
|
+
...(opts.ext ? { ext: opts.ext } : {}),
|
|
35
36
|
}),
|
|
36
|
-
validate: validatePr,
|
|
37
|
+
validate: (parsed) => validatePr(parsed, ctx.ext),
|
|
37
38
|
});
|
|
38
39
|
const { id, dirPath, run } = openPr(ctx, { content: composed.content, fallbackTitle: draft.title }, { commit: opts.commit });
|
|
39
40
|
ctx.stdout.write(`Created ${ctx.navDir}/${dirPath}/ (#${id})\n`);
|
|
@@ -41,72 +42,83 @@ export function cmdPrOpen(ctx, opts) {
|
|
|
41
42
|
if (opts.commit)
|
|
42
43
|
ctx.stdout.write(`${commitReport(run)}\n`);
|
|
43
44
|
}
|
|
44
|
-
/* ------------------------------------------------------------------- update */
|
|
45
45
|
export function cmdPrUpdate(ctx, prefix, opts) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
// The revision pinned is the HEAD of wherever this runs, so moving the write
|
|
47
|
+
// to a worktree on the source branch is what pins the right one — this
|
|
48
|
+
// checkout's HEAD was never the head under review.
|
|
49
|
+
withPrWriteSite(ctx, prefix, opts, (at) => {
|
|
50
|
+
const { entity, head, revisionCount, run } = updatePr(at, prefix, { commit: opts.commit });
|
|
51
|
+
ctx.stdout.write(`Recorded revision ${revisionCount} of #${entity.id} head ${head.slice(0, 12)}\n`);
|
|
52
|
+
if (opts.commit)
|
|
53
|
+
ctx.stdout.write(`${commitReport(run)}\n`);
|
|
54
|
+
});
|
|
50
55
|
}
|
|
51
56
|
export function cmdPrRequest(ctx, prefix, people, opts) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
withPrWriteSite(ctx, prefix, opts, (at) => {
|
|
58
|
+
const { entity, changed, unchanged, run } = requestReview(at, prefix, people, {
|
|
59
|
+
commit: opts.commit,
|
|
60
|
+
remove: opts.remove,
|
|
61
|
+
});
|
|
62
|
+
const verb = opts.remove ? "No longer reviewing" : "Asked to review";
|
|
63
|
+
ctx.stdout.write(`${verb} #${entity.id}: ${changed.join(", ")}\n`);
|
|
64
|
+
// Naming who was already there matters most when only some of a list moved:
|
|
65
|
+
// the count alone would leave the caller counting names themselves.
|
|
66
|
+
for (const person of unchanged) {
|
|
67
|
+
ctx.stdout.write(`${ctx.colors.dim(opts.remove ? "not listed:" : "already listed:")} ${person}\n`);
|
|
68
|
+
}
|
|
69
|
+
if (opts.commit)
|
|
70
|
+
ctx.stdout.write(`${commitReport(run)}\n`);
|
|
55
71
|
});
|
|
56
|
-
const verb = opts.remove ? "No longer reviewing" : "Asked to review";
|
|
57
|
-
ctx.stdout.write(`${verb} #${entity.id}: ${changed.join(", ")}\n`);
|
|
58
|
-
// Naming who was already there matters most when only some of a list moved:
|
|
59
|
-
// the count alone would leave the caller counting names themselves.
|
|
60
|
-
for (const person of unchanged) {
|
|
61
|
-
ctx.stdout.write(`${ctx.colors.dim(opts.remove ? "not listed:" : "already listed:")} ${person}\n`);
|
|
62
|
-
}
|
|
63
|
-
if (opts.commit)
|
|
64
|
-
ctx.stdout.write(`${commitReport(run)}\n`);
|
|
65
72
|
}
|
|
66
73
|
export function cmdPrReview(ctx, prefix, opts) {
|
|
67
|
-
|
|
74
|
+
// Checked before anything is resolved, so a mistyped flag never costs a
|
|
75
|
+
// temporary checkout.
|
|
68
76
|
const chosen = [opts.approve, opts.requestChanges, opts.comment].filter(Boolean).length;
|
|
69
77
|
if (chosen > 1) {
|
|
70
78
|
fail("choose one of --approve, --request-changes or --comment, not several");
|
|
71
79
|
}
|
|
72
|
-
const revision = bindReviewRevision(entity, opts.revision);
|
|
73
80
|
if (opts.line && !opts.file)
|
|
74
81
|
fail("--line needs --file");
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
? "
|
|
83
|
-
: opts.
|
|
84
|
-
? "
|
|
85
|
-
: undefined
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
82
|
+
withPrWriteSite(ctx, prefix, opts, (at, entity) => {
|
|
83
|
+
const revision = bindReviewRevision(entity, opts.revision);
|
|
84
|
+
// This verb files reviews, so with no flag the verdict is the one that judges
|
|
85
|
+
// nothing (spec 04 §4.3). The exception is an inline anchor: a note about one
|
|
86
|
+
// line is discussion, and recording it as a review would say its author had
|
|
87
|
+
// read the whole revision. `nav pr comment` remains the unbound comment.
|
|
88
|
+
const verdict = opts.approve
|
|
89
|
+
? "approve"
|
|
90
|
+
: opts.requestChanges
|
|
91
|
+
? "request-changes"
|
|
92
|
+
: opts.file === undefined || opts.comment
|
|
93
|
+
? "comment"
|
|
94
|
+
: undefined;
|
|
95
|
+
const isReview = verdict !== undefined || opts.file !== undefined;
|
|
96
|
+
const base = {
|
|
97
|
+
author: currentAuthor(at),
|
|
98
|
+
body: "",
|
|
99
|
+
...(verdict ? { verdict } : {}),
|
|
100
|
+
...(isReview ? { revision } : {}),
|
|
101
|
+
...(opts.file ? { file: opts.file } : {}),
|
|
102
|
+
...(opts.line ? { line: opts.line } : {}),
|
|
103
|
+
};
|
|
104
|
+
const composed = composeFile(ctx, {
|
|
105
|
+
message: opts.message,
|
|
106
|
+
bufferName: "NAVBOOK_REVIEW.md",
|
|
107
|
+
noun: verdict ? "review" : "comment",
|
|
108
|
+
render: (body) => newCommentFile({ ...base, body }),
|
|
109
|
+
validate: (parsed) => validateComment(parsed, { onPr: true }),
|
|
110
|
+
});
|
|
111
|
+
const { id, path, run } = applyComment(at, entity, { content: composed.content, review: isReview }, { commit: opts.commit });
|
|
112
|
+
const label = verdict ? `Reviewed (${verdict})` : "Commented on";
|
|
113
|
+
ctx.stdout.write(`${label} #${entity.id} ${at.navDir}/${path} (#${id})\n`);
|
|
114
|
+
if (isReview)
|
|
115
|
+
ctx.stdout.write(`Bound to revision ${revision.slice(0, 12)}\n`);
|
|
116
|
+
if (opts.commit)
|
|
117
|
+
ctx.stdout.write(`${commitReport(run)}\n`);
|
|
118
|
+
// The policy that decides whether a verdict counts is the one in the tree
|
|
119
|
+
// the review was written into.
|
|
120
|
+
warnIfOwnVerdict(at, entity, verdict);
|
|
101
121
|
});
|
|
102
|
-
const { id, path, run } = applyComment(ctx, entity, { content: composed.content, review: isReview }, { commit: opts.commit });
|
|
103
|
-
const label = verdict ? `Reviewed (${verdict})` : "Commented on";
|
|
104
|
-
ctx.stdout.write(`${label} #${entity.id} ${ctx.navDir}/${path} (#${id})\n`);
|
|
105
|
-
if (isReview)
|
|
106
|
-
ctx.stdout.write(`Bound to revision ${revision.slice(0, 12)}\n`);
|
|
107
|
-
if (opts.commit)
|
|
108
|
-
ctx.stdout.write(`${commitReport(run)}\n`);
|
|
109
|
-
warnIfOwnVerdict(ctx, entity, verdict);
|
|
110
122
|
}
|
|
111
123
|
/**
|
|
112
124
|
* Warn an author that their own verdict will not be counted.
|
|
@@ -161,10 +173,13 @@ export function cmdPrList(ctx, terms, opts) {
|
|
|
161
173
|
when: (entities) => entities.some((entity) => summaryOf(entity).reviewers.length > 0),
|
|
162
174
|
},
|
|
163
175
|
];
|
|
176
|
+
// The listing's own columns first, then whatever plugins contributed: the
|
|
177
|
+
// caller's are added to these, never replaced by them.
|
|
178
|
+
const columns = [...extraColumns, ...(opts.extraColumns ?? [])];
|
|
164
179
|
const query = parseListQuery(ctx, terms, "pr");
|
|
165
180
|
if (!opts.allRefs) {
|
|
166
181
|
const here = listEntities(ctx, "pr", query);
|
|
167
|
-
reportList(ctx, "pr", here, { ...opts, extraColumns });
|
|
182
|
+
reportList(ctx, "pr", here, { ...opts, extraColumns: columns });
|
|
168
183
|
if (here.length === 0)
|
|
169
184
|
hintOtherRefs(ctx, opts);
|
|
170
185
|
return;
|
|
@@ -173,7 +188,10 @@ export function cmdPrList(ctx, terms, opts) {
|
|
|
173
188
|
if (opts.json) {
|
|
174
189
|
if (matched.length === 0)
|
|
175
190
|
return;
|
|
176
|
-
ctx.stdout.write(`${toNdjson(matched.map((entry) => entityJson(ctx.navDir, entry.entity, {
|
|
191
|
+
ctx.stdout.write(`${toNdjson(matched.map((entry) => entityJson(ctx.navDir, entry.entity, {
|
|
192
|
+
...(opts.jsonExtra?.(entry.entity) ?? {}),
|
|
193
|
+
refs: entry.refs.map((ref) => ref.short),
|
|
194
|
+
})))}\n`);
|
|
177
195
|
return;
|
|
178
196
|
}
|
|
179
197
|
if (matched.length === 0) {
|
|
@@ -183,7 +201,7 @@ export function cmdPrList(ctx, terms, opts) {
|
|
|
183
201
|
reportList(ctx, "pr", listEntities(ctx, "pr", query, { entities: matched.map((e) => e.entity) }), {
|
|
184
202
|
...opts,
|
|
185
203
|
extraColumns: [
|
|
186
|
-
...
|
|
204
|
+
...columns,
|
|
187
205
|
{
|
|
188
206
|
header: "refs",
|
|
189
207
|
value: (entity) => (matched.find((entry) => entry.entity.id === entity.id)?.refs ?? [])
|
|
@@ -208,7 +226,15 @@ export function cmdPrList(ctx, terms, opts) {
|
|
|
208
226
|
function hintOtherRefs(ctx, opts) {
|
|
209
227
|
if (opts.json)
|
|
210
228
|
return;
|
|
211
|
-
|
|
229
|
+
let count;
|
|
230
|
+
try {
|
|
231
|
+
count = countOpenPrsOnOtherRefs(ctx);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// The listing this follows has already been printed and is right; a branch
|
|
235
|
+
// git cannot read is `--all-refs`'s to report, not a reason to fail this.
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
212
238
|
if (count === 0)
|
|
213
239
|
return;
|
|
214
240
|
ctx.stderr.write(`${ctx.colors.dim(`${count} open pull ${count === 1 ? "request" : "requests"} on other branches; ` +
|