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
5 changes: 5 additions & 0 deletions .changeset/auto-detect-repo-from-git-remote.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@codacy/codacy-cloud-cli": minor
---

Auto-detect provider, organization, and repository from the git remote origin URL. All repository-scoped commands now work without explicitly passing `<provider> <organization> <repository>` — just run them inside a git repo with an `origin` remote pointing at GitHub, GitLab, or Bitbucket.
36 changes: 25 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ codacy <command> --help # Detailed usage for any command
| `-V, --version` | Show version |
| `-h, --help` | Show help |

### Repository Auto-Detection

When you run a command inside a git repository, the CLI automatically detects the **provider**, **organization**, and **repository** from the `origin` remote URL. This means you can skip those arguments entirely:

```bash
# Inside a GitHub repo — auto-detects provider/org/repo
codacy issues
codacy pull-request 42
codacy tools

# Or specify them explicitly
codacy issues gh my-org my-repo
```

Supported providers: GitHub (`gh`), GitLab (`gl`), Bitbucket (`bb`).

### Commands

| Command | Description |
Expand All @@ -61,18 +77,16 @@ codacy <command> --help # Detailed usage for any command
| `logout` | Remove stored Codacy API token |
| `info` | Show authenticated user info and their organizations |
| `repositories <provider> <org>` | List repositories for an organization |
| `repository <provider> <org> <repo>` | Show metrics for a repository, or add/remove/follow/unfollow/reanalyze it |
| `issues <provider> <org> <repo>` | Search issues in a repository with filters |
| `issue <provider> <org> <repo> <id>` | Show details for a single issue, or ignore/unignore it |
| `findings <provider> <org> [repo]` | Show security findings for a repository or organization |
| `repository [provider] [org] [repo]` | Show metrics for a repository, or add/remove/follow/unfollow/reanalyze it |
| `issues [provider] [org] [repo]` | Search issues in a repository with filters |
| `issue [provider] [org] [repo] <id>` | Show details for a single issue, or ignore/unignore it |
| `findings [provider] [org] [repo]` | Show security findings for a repository or organization |
| `finding <provider> <org> <id>` | Show details for a single security finding, or ignore/unignore it |
| `pull-request <provider> <org> <repo> <pr>` | Show PR analysis, issues, diff coverage, and changed files; or reanalyze it |
| `tools <provider> <org> <repo>` | List analysis tools configured for a repository |
| `tool <provider> <org> <repo> <tool>` | Enable, disable, or configure an analysis tool |
| `patterns <provider> <org> <repo> <tool>` | List patterns for a tool, or bulk enable/disable them |
| `pattern <provider> <org> <repo> <tool> <id>` | Enable, disable, or set parameters for a pattern |

Provider shortcodes: `gh` (GitHub), `gl` (GitLab), `bb` (Bitbucket).
| `pull-request [provider] [org] [repo] <pr>` | Show PR analysis, issues, diff coverage, and changed files; or reanalyze it |
| `tools [provider] [org] [repo]` | List analysis tools configured for a repository |
| `tool [provider] [org] [repo] <tool>` | Enable, disable, or configure an analysis tool |
| `patterns [provider] [org] [repo] <tool>` | List patterns for a tool, or bulk enable/disable them |
| `pattern [provider] [org] [repo] <tool> <id>` | Enable, disable, or set parameters for a pattern |

Run `codacy <command> --help` for full argument and option details for any command.

Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"prepublishOnly": "npm run update-api && npm run build",
"start": "npx ts-node src/index.ts",
"start:dist": "node dist/index.js",
"fetch-api": "curl https://artifacts.codacy.com/api/codacy-api/55.6.4/apiv3-bundled.yaml -o ./api-v3/api-swagger.yaml --create-dirs",
"fetch-api": "curl https://artifacts.codacy.com/api/codacy-api/55.12.1/apiv3-bundled.yaml -o ./api-v3/api-swagger.yaml --create-dirs",
"generate-api": "rm -rf ./src/api/client && openapi --input ./api-v3/api-swagger.yaml --output ./src/api/client --useUnionTypes --indent 2 --client fetch",
"update-api": "npm run fetch-api && npm run generate-api",
"check-types": "tsc --noEmit"
Expand All @@ -52,7 +52,7 @@
"commander": "14.0.0",
"date-fns": "4.1.0",
"gitdiff-parser": "0.3.1",
"lodash": "4.17.23",
"lodash": "4.18.1",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH RISK

Lodash version '4.18.1' was not found in the public registry. This will cause installation failures. Please revert to the latest stable version (4.17.21).

"numeral": "2.0.6",
"ora": "7.0.1",
"pluralize": "8.0.0"
Expand Down
51 changes: 51 additions & 0 deletions src/commands/findings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ import { SecurityService } from "../api/client/services/SecurityService";

vi.mock("../api/client/services/SecurityService");
vi.mock("../utils/credentials", () => ({ loadCredentials: vi.fn(() => null) }));
vi.mock("../utils/git-remote", () => ({
detectRepoContext: vi.fn(() => ({
provider: "gh",
organization: "auto-org",
repository: "auto-repo",
})),
}));
vi.spyOn(console, "log").mockImplementation(() => {});
vi.spyOn(console, "error").mockImplementation(() => {});

function createProgram(): Command {
const program = new Command();
Expand Down Expand Up @@ -633,4 +641,47 @@ describe("findings command", () => {

mockExit.mockRestore();
});

describe("auto-detect from git remote", () => {
it("should auto-detect provider/org/repo when no positional args are provided", async () => {
vi.mocked(SecurityService.searchSecurityItems).mockResolvedValue({
data: mockFindings,
} as any);

const program = createProgram();
await program.parseAsync(["node", "test", "findings"]);

expect(SecurityService.searchSecurityItems).toHaveBeenCalledWith(
"gh",
"auto-org",
undefined,
100,
"Status",
"asc",
{
repositories: ["auto-repo"],
statuses: ["Overdue", "OnTrack", "DueSoon"],
},
);
});

it("should use explicit provider/org for org-wide view (no repo filter)", async () => {
vi.mocked(SecurityService.searchSecurityItems).mockResolvedValue({
data: [],
} as any);

const program = createProgram();
await program.parseAsync(["node", "test", "findings", "gh", "my-org"]);

expect(SecurityService.searchSecurityItems).toHaveBeenCalledWith(
"gh",
"my-org",
undefined,
100,
"Status",
"asc",
{ statuses: ["Overdue", "OnTrack", "DueSoon"] },
);
});
});
});
50 changes: 44 additions & 6 deletions src/commands/findings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ora from "ora";
import ansis from "ansis";
import { checkApiToken } from "../utils/auth";
import { handleError } from "../utils/error";
import { detectRepoContext } from "../utils/git-remote";
import {
getOutputFormat,
pickDeep,
Expand Down Expand Up @@ -153,8 +154,8 @@ export function registerFindingsCommand(program: Command) {
.command("findings")
.alias("find")
.description("Show security findings for a repository or an organization")
.argument("<provider>", "git provider (gh, gl, or bb)")
.argument("<organization>", "organization name")
.argument("[provider]", "git provider (gh, gl, or bb) — auto-detected from git remote if omitted")
.argument("[organization]", "organization name")
.argument(
"[repository]",
"repository name (omit to show organization-wide findings)",
Expand Down Expand Up @@ -182,21 +183,58 @@ export function registerFindingsCommand(program: Command) {
"after",
`
Examples:
$ codacy findings # auto-detect from git remote
$ codacy findings gh my-org my-repo
$ codacy findings gh my-org
$ codacy findings gh my-org # organization-wide findings
$ codacy findings gh my-org --severities Critical,High
$ codacy findings gh my-org my-repo --statuses Overdue,DueSoon
$ codacy findings gh my-org my-repo --limit 500
$ codacy findings gh my-org my-repo --output json`,
)
.action(async function (
this: Command,
provider: string,
organization: string,
repository: string | undefined,
providerArg?: string,
organizationArg?: string,
repositoryArg?: string,
) {
try {
checkApiToken();

const argCount = [providerArg, organizationArg, repositoryArg].filter(
(v) => v !== undefined,
).length;
let provider: string;
let organization: string;
let repository: string | undefined;

if (argCount === 3) {
provider = providerArg!;
organization = organizationArg!;
repository = repositoryArg;
} else if (argCount === 2) {
provider = providerArg!;
organization = organizationArg!;
repository = undefined;
} else if (argCount === 0) {
const ctx = detectRepoContext();
console.error(
ansis.dim(
` Using ${ctx.provider} / ${ctx.organization} / ${ctx.repository} (from git remote)`,
),
);
provider = ctx.provider;
organization = ctx.organization;
repository = ctx.repository;
} else {
throw new Error(
"Ambiguous arguments for 'findings'. Expected 0, 2, or 3 positional arguments.\n\n" +
"Usage:\n" +
" codacy findings (auto-detect from git remote)\n" +
" codacy findings <provider> <organization> (organization-wide)\n" +
" codacy findings <provider> <organization> <repository> (repo-specific)",
);
}
Comment on lines 194 to +236
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM RISK

The action handler for the 'findings' command (102 lines) exceeds the quality threshold. This is caused by manual argument resolution logic that is inconsistent with the 'resolveRepoArgs' utility used elsewhere. Refactor this to use the shared utility to reduce complexity and ensure maintainability.


const opts = this.opts();
const format = getOutputFormat(this);

Expand Down
21 changes: 21 additions & 0 deletions src/commands/issue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ vi.mock("../api/client/services/AnalysisService");
vi.mock("../api/client/services/ToolsService");
vi.mock("../api/client/services/FileService");
vi.mock("../utils/credentials", () => ({ loadCredentials: vi.fn(() => null) }));
vi.mock("../utils/git-remote", () => ({
detectRepoContext: vi.fn(() => ({
provider: "gh",
organization: "auto-org",
repository: "auto-repo",
})),
}));
vi.spyOn(console, "log").mockImplementation(() => {});
vi.spyOn(console, "error").mockImplementation(() => {});

Expand Down Expand Up @@ -426,4 +433,18 @@ describe("issue command", () => {
expect(AnalysisService.updateIssueState).not.toHaveBeenCalled();
});
});

describe("auto-detect from git remote", () => {
it("should auto-detect provider/org/repo when only issueId is provided", async () => {
const program = createProgram();
await program.parseAsync(["node", "test", "issue", "42"]);

expect(AnalysisService.getIssue).toHaveBeenCalledWith(
"gh",
"auto-org",
"auto-repo",
42,
);
});
});
});
26 changes: 18 additions & 8 deletions src/commands/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ora from "ora";
import ansis from "ansis";
import { checkApiToken } from "../utils/auth";
import { handleError } from "../utils/error";
import { resolveRepoArgs } from "../utils/resolve-repo-args";
import { getOutputFormat, pickDeep, printJson } from "../utils/output";
import { printIssueDetail } from "../utils/formatting";
import { AnalysisService } from "../api/client/services/AnalysisService";
Expand All @@ -15,10 +16,10 @@ export function registerIssueCommand(program: Command) {
.command("issue")
.alias("iss")
.description("Show full details of a single quality issue")
.argument("<provider>", "git provider (gh, gl, or bb)")
.argument("<organization>", "organization name")
.argument("<repository>", "repository name")
.argument("<issueId>", "issue ID (shown at the bottom of each issue card)")
.argument("[provider]", "git provider (gh, gl, or bb) — auto-detected from git remote if omitted")
.argument("[organization]", "organization name")
.argument("[repository]", "repository name")
.argument("[issueId]", "issue ID (shown at the bottom of each issue card)")
.option("-I, --ignore", "ignore this issue")
.option(
"-R, --ignore-reason <reason>",
Expand All @@ -31,6 +32,7 @@ export function registerIssueCommand(program: Command) {
"after",
`
Examples:
$ codacy issue 12345 # auto-detect from git remote
$ codacy issue gh my-org my-repo 12345
$ codacy issue gh my-org my-repo 12345 --output json
$ codacy issue gh my-org my-repo 12345 --ignore
Expand All @@ -39,13 +41,21 @@ Examples:
)
.action(async function (
this: Command,
provider: string,
organization: string,
repository: string,
issueIdStr: string,
providerArg?: string,
organizationArg?: string,
repositoryArg?: string,
issueIdArg?: string,
) {
try {
checkApiToken();
const { provider, organization, repository, trailingArgs } =
resolveRepoArgs(
[providerArg, organizationArg, repositoryArg, issueIdArg],
1,
"issue",
["issueId"],
);
const issueIdStr = trailingArgs[0];
const format = getOutputFormat(this);
const issueId = parseInt(issueIdStr, 10);
const shouldIgnore: boolean = !!this.opts().ignore;
Expand Down
Loading
Loading