Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
</p>

<p align="center">
<img src="https://raw.githubusercontent.com/hyodotdev/gitdog-cli/main/.github/assets/terminal.svg?v=0.1.2" alt="Gitdog terminal: connect a project, configure automation and review pending actions" width="800">
<img src="https://raw.githubusercontent.com/hyodotdev/gitdog-cli/main/.github/assets/terminal.svg?v=0.2.0" alt="Gitdog terminal: connect a project, configure automation and review pending actions" width="800">
</p>

Connect repositories, configure Gitdog's issue and discussion assistants, review
AI actions, and automate project tasks from your terminal or CI.
Connect repositories, configure issue, discussion and PR automation, review
AI actions, and manage project tasks from your terminal or CI.

## Run with npx

Expand All @@ -39,6 +39,8 @@ npx gitdog@latest github install
npx gitdog@latest github recover
npx gitdog@latest init owner/repo
npx gitdog@latest config set requireApproval true
npx gitdog@latest config set pullRequestReviewsEnabled true
npx gitdog@latest config set requireApprovalForPullRequests true
npx gitdog@latest config push
```

Expand All @@ -62,7 +64,7 @@ Use `npx gitdog@latest --help` to explore commands. Add `--json` for scripts.

```sh
npx gitdog@latest keys create --name ci
npx --yes gitdog@0.1.2 --key-file /path/to/key.json projects list --json
npx --yes gitdog@0.2.0 --key-file /path/to/key.json projects list --json
npx gitdog@latest keys revoke KEY_ID --yes
```

Expand Down
20 changes: 20 additions & 0 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,23 @@ The npm trusted publisher is restricted to `hyodotdev/gitdog-cli`, `release.yml`
and the `npm` GitHub environment. The workflow verifies the registry archive and
signed provenance against the release tag and commit. A rerun verifies an already
published matching archive instead of republishing it.

### Pull request reviews

```sh
npx gitdog@latest config set pullRequestReviewsEnabled true
npx gitdog@latest config set pullRequestSuggestionsEnabled true
npx gitdog@latest config set requireApprovalForPullRequests true
npx gitdog@latest config push --dry-run
npx gitdog@latest config push
npx gitdog@latest tickets pending --type pull_request
```

Reviews are opt-in and require GitHub App pull-request write permission.
Proposals appear in Tickets; approval posts inline comments on the reviewed
commit. GitHub commit suggestions can be applied individually or as a batch by authors
or reviewers with write access. Disable them with
`config set pullRequestSuggestionsEnabled false`. Gitdog does not approve merges,
commit changes automatically or execute PR code. Incomplete coverage
and failed reviews remain visible. New settings require the corresponding
Gitdog server release.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gitdog",
"version": "0.1.2",
"version": "0.2.0",
"description": "Manage Gitdog projects, GitHub automation and API keys from your terminal",
"type": "module",
"bin": {
Expand Down
6 changes: 3 additions & 3 deletions src/api-catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@
{
"path": "repositories/mutation:updateSettings",
"kind": "mutation",
"args": "{\n repositoryId: v.id(\"repositories\"),\n settings: v.object({\n issueAssistantEnabled: v.boolean(),\n autoFormatIssue: v.boolean(),\n autoAnswerQuestions: v.boolean(),\n tagMaintainersForCodeChanges: v.boolean(),\n issueResponseStyle: v.optional(v.string()),\n requireApproval: v.boolean(),\n discussionAssistantEnabled: v.boolean(),\n autoFormatDiscussion: v.boolean(),\n autoAnswerDiscussionQuestions: v.boolean(),\n tagMaintainersForDiscussionCodeChanges: v.boolean(),\n discussionResponseStyle: v.optional(v.string()),\n requireApprovalForDiscussions: v.boolean(),\n }),\n }"
"args": "{\n repositoryId: v.id(\"repositories\"),\n settings: v.object({\n issueAssistantEnabled: v.boolean(),\n autoFormatIssue: v.boolean(),\n autoAnswerQuestions: v.boolean(),\n tagMaintainersForCodeChanges: v.boolean(),\n issueResponseStyle: v.optional(v.string()),\n requireApproval: v.boolean(),\n discussionAssistantEnabled: v.boolean(),\n autoFormatDiscussion: v.boolean(),\n autoAnswerDiscussionQuestions: v.boolean(),\n tagMaintainersForDiscussionCodeChanges: v.boolean(),\n discussionResponseStyle: v.optional(v.string()),\n requireApprovalForDiscussions: v.boolean(),\n pullRequestReviewsEnabled: v.optional(v.boolean()),\n pullRequestSuggestionsEnabled: v.optional(v.boolean()),\n requireApprovalForPullRequests: v.optional(v.boolean()),\n }),\n }"
},
{
"path": "repositories/query:get",
Expand Down Expand Up @@ -387,12 +387,12 @@
{
"path": "tickets/query:list",
"kind": "query",
"args": "{\n githubRepo: v.optional(v.string()),\n status: v.optional(v.union(v.literal(\"open\"), v.literal(\"closed\"))),\n ticketType: v.optional(\n v.union(v.literal(\"issue\"), v.literal(\"discussion\")),\n ),\n search: v.optional(v.string()),\n limit: v.optional(v.number()),\n offset: v.optional(v.number()),\n }"
"args": "{\n githubRepo: v.optional(v.string()),\n status: v.optional(v.union(v.literal(\"open\"), v.literal(\"closed\"))),\n ticketType: v.optional(\n v.union(\n v.literal(\"issue\"),\n v.literal(\"discussion\"),\n v.literal(\"pull_request\"),\n ),\n ),\n search: v.optional(v.string()),\n limit: v.optional(v.number()),\n offset: v.optional(v.number()),\n }"
},
{
"path": "tickets/query:listPendingApproval",
"kind": "query",
"args": "{\n githubRepo: v.optional(v.string()),\n ticketType: v.optional(\n v.union(v.literal(\"issue\"), v.literal(\"discussion\")),\n ),\n search: v.optional(v.string()),\n limit: v.optional(v.number()),\n offset: v.optional(v.number()),\n }"
"args": "{\n githubRepo: v.optional(v.string()),\n ticketType: v.optional(\n v.union(\n v.literal(\"issue\"),\n v.literal(\"discussion\"),\n v.literal(\"pull_request\"),\n ),\n ),\n search: v.optional(v.string()),\n limit: v.optional(v.number()),\n offset: v.optional(v.number()),\n }"
},
{
"path": "tokenUsage/query:getCurrentMonthUsage",
Expand Down
7 changes: 5 additions & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,14 +737,16 @@ export function createProgram(

const tickets = program
.command("tickets")
.description("Browse issues, discussions and pending approvals");
.description(
"Browse issues, discussions, PR reviews and pending approvals",
);
for (const pending of [false, true]) {
command(
tickets,
pending ? "pending" : "list",
pending
? "List tickets awaiting approval"
: "List issues and discussions",
: "List issues, discussions and PR reviews",
async ({ api, project, ui }, _args, opts) => {
const githubRepo = flag(opts.all)
? undefined
Expand Down Expand Up @@ -776,6 +778,7 @@ export function createProgram(
new Option("--type <type>", "Ticket type").choices([
"issue",
"discussion",
"pull_request",
]),
)
.option("--search <text>", "Search title, repository or GitHub number")
Expand Down
15 changes: 14 additions & 1 deletion src/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { asRecord, integer, type Ui } from "./ui.js";
import type { Api } from "./client.js";

export const BOOLEAN_SETTINGS = [
"pullRequestReviewsEnabled",
"pullRequestSuggestionsEnabled",
"requireApprovalForPullRequests",
"issueAssistantEnabled",
"autoFormatIssue",
"autoAnswerQuestions",
Expand Down Expand Up @@ -82,11 +85,21 @@ export function validateSettings(
}
if (
complete &&
BOOLEAN_SETTINGS.some((key) => typeof settings[key] !== "boolean")
BOOLEAN_SETTINGS.filter(
(key) =>
key !== "pullRequestReviewsEnabled" &&
key !== "pullRequestSuggestionsEnabled" &&
key !== "requireApprovalForPullRequests",
).some((key) => typeof settings[key] !== "boolean")
)
throw new Error(
"The server returned incomplete project settings. Update Gitdog before applying settings.",
);
if (complete) {
settings.pullRequestReviewsEnabled ??= false;
settings.pullRequestSuggestionsEnabled ??= true;
settings.requireApprovalForPullRequests ??= true;
}
return settings;
}

Expand Down
2 changes: 1 addition & 1 deletion src/ui.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createInterface } from "node:readline/promises";
import { stripVTControlCharacters } from "node:util";

export const VERSION = "0.1.2";
export const VERSION = "0.2.0";
export const DOG = String.raw`
▗██▄▄ ▄▄██▖
██▀▜██▙▄ ▙▄ ▄▟██▛▀██
Expand Down
6 changes: 6 additions & 0 deletions test/commands.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ const scenarios = [
offset: 2,
},
],
[
"tickets pending --all --type pull_request",
"query",
"tickets/query:listPendingApproval",
{ ticketType: "pull_request", limit: 20, offset: 0 },
],
[
"tickets pending --all --type discussion",
"query",
Expand Down
28 changes: 28 additions & 0 deletions test/projects.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,31 @@ test("project discovery stops at the repository root", async (t) => {
join(checkout, "gitdog.json"),
);
});

test("PR review settings accept old server defaults and strict booleans", () => {
const legacy = Object.fromEntries(
Object.entries(settings).filter(
([k]) =>
![
"pullRequestReviewsEnabled",
"pullRequestSuggestionsEnabled",
"requireApprovalForPullRequests",
].includes(k),
),
);
const current = validateSettings(legacy, true);
assert.equal(current.pullRequestReviewsEnabled, false);
assert.equal(current.pullRequestSuggestionsEnabled, true);
assert.equal(current.requireApprovalForPullRequests, true);
assert.deepEqual(
validateSettings({
pullRequestReviewsEnabled: true,
requireApprovalForPullRequests: false,
}),
{ pullRequestReviewsEnabled: true, requireApprovalForPullRequests: false },
);
assert.throws(
() => validateSettings({ pullRequestReviewsEnabled: "true" }),
/true or false/,
);
});
Loading