Skip to content

feat: import config#3

Merged
alerizzo merged 3 commits intomainfrom
feat-import-config
Mar 26, 2026
Merged

feat: import config#3
alerizzo merged 3 commits intomainfrom
feat-import-config

Conversation

@alerizzo
Copy link
Copy Markdown
Collaborator

Added import config from the file generated by the analysis-cli into a remote repository

Copilot AI review requested due to automatic review settings March 26, 2026 14:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for importing a Codacy tool configuration file into a remote repository, plus related CLI UX improvements (confirmation prompt, standards management) and pagination controls for listing issues/findings.

Changes:

  • Add tools --import flow to preview and apply a local config file to a repository (with confirmation and --force standard unlinking).
  • Add repository --link-standard/--unlink-standard actions and show coding standard IDs in repository output.
  • Add --limit pagination support for issues and findings commands (up to 1000).

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/utils/prompt.ts Adds a reusable confirmation prompt for CLI actions.
src/utils/import-config.ts Implements config parsing, preview generation, and apply/execute logic for tool imports.
src/utils/import-config.test.ts Adds unit tests for the import-config helper functions.
src/types/codacy-config.ts Re-exports config types from @codacy/tooling.
src/commands/tools.ts Adds --import, --skip-approval, --force handling and integrates import execution into the tools command.
src/commands/tools.test.ts Adds tests covering tools import mode behavior.
src/commands/repository.ts Shows standard IDs and adds link/unlink standard actions.
src/commands/repository.test.ts Adds tests for standard ID display and link/unlink actions.
src/commands/issues.ts Adds --limit and paginated fetching for issues results.
src/commands/issues.test.ts Adds tests for issues pagination/limit behavior and JSON output fields.
src/commands/findings.ts Adds --limit and paginated fetching for findings results.
src/commands/findings.test.ts Adds tests for findings pagination/limit behavior.
package.json Bumps version and adds @codacy/tooling dependency.
package-lock.json Updates lockfile for the new dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 42 to 47
"engines": {
"node": ">=18"
},
"dependencies": {
"@codacy/tooling": "0.1.0",
"ansis": "4.0.0",
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The new dependency @codacy/tooling@0.1.0 requires Node >=20.0.0 (per package-lock), but this package still declares engines.node: >=18. This will cause installs on Node 18/19 to fail or be rejected by tooling that enforces engines. Align the CLI's supported Node version with the dependency (bump engines to >=20) or use a @codacy/tooling version that supports Node 18.

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +111
const spinner = ora("Reading configuration...").start();

// Read config file
const config = readConfigFile(resolvedPath);

// Fetch current state in parallel
const [repoToolsResponse, allTools, repoResponse] =
await Promise.all([
AnalysisService.listRepositoryTools(
provider,
organization,
repository,
),
fetchAllTools(),
AnalysisService.getRepositoryWithAnalysis(
provider,
organization,
repository,
),
]);
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

In import mode, the spinner is started before reading/parsing the config and before the parallel API calls. If readConfigFile(...) throws or Promise.all(...) rejects, execution jumps to the outer catch and process.exit(1) runs without stopping the spinner, leaving the terminal in a bad state. Wrap the import block in a try/finally (or use spinner.fail(...)) so the spinner is always stopped/cleared on errors.

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +85
],
};
const tmpPath = "/tmp/test-codacy-config.json";
fs.writeFileSync(tmpPath, JSON.stringify(config));
const result = readConfigFile(tmpPath);
expect(result.version).toBe(1);
expect(result.tools).toHaveLength(1);
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

These tests write to hard-coded /tmp/... paths, which is not portable (e.g., Windows runners) and can cause collisions between parallel test runs. Prefer os.tmpdir() + path.join(...) (and ideally include process.pid/random suffix) to create unique temp file paths.

Copilot uses AI. Check for mistakes.
Comment on lines +266 to +270
const tmpConfigPath = "/tmp/test-import-config.json";

beforeEach(() => {
fs.writeFileSync(tmpConfigPath, configContent);
vi.mocked(AnalysisService.updateRepositoryToolPatterns).mockResolvedValue(undefined as any);
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The import-mode tests use a hard-coded /tmp/test-import-config.json path. This is not portable and can be flaky under parallel test execution. Use os.tmpdir()/path.join(...) and a unique filename (pid/random) for the temp config file.

Copilot uses AI. Check for mistakes.
const config = JSON.parse(raw) as CodacyConfig;
if (!config.version || !Array.isArray(config.tools)) {
throw new Error("Invalid configuration file: missing 'version' or 'tools' fields.");
}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

readConfigFile only validates top-level version and tools, but later code assumes every tool has a patterns array (e.g., t.patterns.length, toolConfig.patterns.map(...)). A config missing patterns (or with non-array values) will crash with a less helpful runtime error. Consider validating each tools[] entry (e.g., toolId string and patterns as an array, defaulting to [] when absent) and throwing a clear error pointing to the offending entry.

Suggested change
}
}
// Validate and normalize each tool configuration
config.tools = config.tools.map((tool, index) => {
if (!tool || typeof tool !== "object") {
throw new Error(
`Invalid configuration file: tools[${index}] must be an object.`,
);
}
const anyTool = tool as any;
const toolId = anyTool.toolId;
if (typeof toolId !== "string" || toolId.trim() === "") {
throw new Error(
`Invalid configuration file: tools[${index}] is missing a valid 'toolId' string.`,
);
}
const patterns = anyTool.patterns;
if (patterns === undefined) {
// Default to empty array when patterns are not specified
return {
...anyTool,
patterns: [],
} as CodacyToolConfig;
}
if (!Array.isArray(patterns)) {
throw new Error(
`Invalid configuration file: 'patterns' for tool '${toolId}' must be an array.`,
);
}
return tool as CodacyToolConfig;
});

Copilot uses AI. Check for mistakes.
Comment on lines +347 to +354
if (opts.linkStandard) {
const spinner = ora(`Linking coding standard #${opts.linkStandard} to ${repository}...`).start();
await CodingStandardsService.applyCodingStandardToRepositories(
provider,
organization,
Number(opts.linkStandard),
{ link: [repository], unlink: [] },
);
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

--link-standard/--unlink-standard convert the provided id with Number(...) without validating it. If the user passes a non-numeric value, Number() becomes NaN and the API call will be made with an invalid standard id. Parse with parseInt and fail fast with a clear error when the id is not a finite integer.

Copilot uses AI. Check for mistakes.
Comment on lines +349 to +375
await CodingStandardsService.applyCodingStandardToRepositories(
provider,
organization,
Number(opts.linkStandard),
{ link: [repository], unlink: [] },
);
spinner.stop();
console.log(
`${ansis.green("✓")} Coding standard #${opts.linkStandard} linked to ${ansis.bold(repository)}.`,
);
return;
}

// ── Action: unlink-standard ───────────────────────────────────────
if (opts.unlinkStandard) {
const spinner = ora(`Unlinking coding standard #${opts.unlinkStandard} from ${repository}...`).start();
await CodingStandardsService.applyCodingStandardToRepositories(
provider,
organization,
Number(opts.unlinkStandard),
{ link: [], unlink: [repository] },
);
spinner.stop();
console.log(
`${ansis.green("✓")} Coding standard #${opts.unlinkStandard} unlinked from ${ansis.bold(repository)}.`,
);
return;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The new link/unlink-standard actions start a spinner and await the API call, but don't stop/fail the spinner if the request throws. On error, the outer catch exits the process, which can leave the spinner running and the terminal output garbled. Use a local try/catch/finally around the API call to ensure the spinner is stopped (and optionally show spinner.fail(...)) before rethrowing/handling the error.

Suggested change
await CodingStandardsService.applyCodingStandardToRepositories(
provider,
organization,
Number(opts.linkStandard),
{ link: [repository], unlink: [] },
);
spinner.stop();
console.log(
`${ansis.green("✓")} Coding standard #${opts.linkStandard} linked to ${ansis.bold(repository)}.`,
);
return;
}
// ── Action: unlink-standard ───────────────────────────────────────
if (opts.unlinkStandard) {
const spinner = ora(`Unlinking coding standard #${opts.unlinkStandard} from ${repository}...`).start();
await CodingStandardsService.applyCodingStandardToRepositories(
provider,
organization,
Number(opts.unlinkStandard),
{ link: [], unlink: [repository] },
);
spinner.stop();
console.log(
`${ansis.green("✓")} Coding standard #${opts.unlinkStandard} unlinked from ${ansis.bold(repository)}.`,
);
return;
try {
await CodingStandardsService.applyCodingStandardToRepositories(
provider,
organization,
Number(opts.linkStandard),
{ link: [repository], unlink: [] },
);
spinner.stop();
console.log(
`${ansis.green("✓")} Coding standard #${opts.linkStandard} linked to ${ansis.bold(repository)}.`,
);
return;
} catch (linkStandardErr) {
spinner.fail(
`Failed to link coding standard #${opts.linkStandard} to ${repository}: ${
linkStandardErr instanceof Error ? linkStandardErr.message : linkStandardErr
}`,
);
throw linkStandardErr;
}
}
// ── Action: unlink-standard ───────────────────────────────────────
if (opts.unlinkStandard) {
const spinner = ora(`Unlinking coding standard #${opts.unlinkStandard} from ${repository}...`).start();
try {
await CodingStandardsService.applyCodingStandardToRepositories(
provider,
organization,
Number(opts.unlinkStandard),
{ link: [], unlink: [repository] },
);
spinner.stop();
console.log(
`${ansis.green("✓")} Coding standard #${opts.unlinkStandard} unlinked from ${ansis.bold(repository)}.`,
);
return;
} catch (unlinkStandardErr) {
spinner.fail(
`Failed to unlink coding standard #${opts.unlinkStandard} from ${repository}: ${
unlinkStandardErr instanceof Error ? unlinkStandardErr.message : unlinkStandardErr
}`,
);
throw unlinkStandardErr;
}

Copilot uses AI. Check for mistakes.
@alerizzo alerizzo merged commit 03d2f5f into main Mar 26, 2026
3 checks passed
@alerizzo alerizzo deleted the feat-import-config branch March 26, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants