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
13 changes: 12 additions & 1 deletion src/commands/init-ci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ function providerLabel(provider: string): string {
return PROVIDER_LABELS[provider] ?? "CI Workflow";
}

const VALID_CI_PROVIDERS = Object.keys(CI_FILE_PATHS);

function validateCiProvider(provider: string | undefined): void {
if (provider !== undefined && !VALID_CI_PROVIDERS.includes(provider)) {
throw new Error(`Invalid --ci-provider "${provider}". Valid options: ${VALID_CI_PROVIDERS.join(", ")}.`);
}
}

const DEFAULT_WORKFLOW_PATH = ".github/workflows/mcp-observatory.yml";
const DEFAULT_BADGE_PATH = "docs/mcp-observatory-badge.md";
const DEFAULT_TARGET_CONFIG_PATH = "mcp-observatory.target.json";
Expand Down Expand Up @@ -495,6 +503,7 @@ function doctorFixOptions(options: InitCiOptions, workflow: string | undefined):
}

export async function doctorSetupCi(options: InitCiOptions = {}): Promise<SetupCiDoctorResult> {
validateCiProvider(options.ciProvider);
const detectedProvider = options.ciProvider ?? detectCiProvider() ?? "github-actions";
const defaultPath = CI_FILE_PATHS[detectedProvider] ?? DEFAULT_WORKFLOW_PATH;
const workflowPath = options.workflow ?? defaultPath;
Expand Down Expand Up @@ -595,6 +604,7 @@ export async function doctorSetupCi(options: InitCiOptions = {}): Promise<SetupC
}

export async function initCi(options: InitCiOptions): Promise<InitCiResult> {
validateCiProvider(options.ciProvider);
if (options.command && options.target) {
throw new Error("Use either --command or --target, not both.");
}
Expand Down Expand Up @@ -644,7 +654,8 @@ function addInitCiOptions(command: Command): Command {
return command
.option("--command <command>", "MCP server command to test, for example: 'npx -y my-mcp-server'")
.option("--target <file>", "Target config JSON path to use instead of a command.")
.option("--workflow <file>", "Workflow output path.", DEFAULT_WORKFLOW_PATH)
.option("--workflow <file>", "Workflow output path. Defaults to the path for the detected or selected CI provider.")
.option("--ci-provider <provider>", `Override auto-detected CI provider (${VALID_CI_PROVIDERS.join(", ")}).`)
.option("--badge", "Also write a README badge snippet.", false)
.option("--badge-file <file>", "Badge snippet output path.", DEFAULT_BADGE_PATH)
.option("--target-config [file]", "Also write an example target config and point the workflow at it.")
Expand Down
41 changes: 41 additions & 0 deletions tests/init-ci.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,4 +287,45 @@ describe("init-ci", () => {
it("rejects target config generation when an existing target is supplied", async () => {
await expect(initCi({ target: "./target.json", targetConfig: true })).rejects.toThrow("Use either --target or --target-config");
});

it("honors an explicit --ci-provider override instead of auto-detecting", async () => {
const dir = await tempDir();
const workflow = path.join(dir, ".gitlab-ci.yml");

const result = await initCi({
command: "npx -y @example/mcp-server",
workflow,
ciProvider: "gitlab-ci",
});

expect(result.workflowStatus).toBe("created");
const workflowText = await readFile(workflow, "utf8");
expect(workflowText).toContain("mcp-observatory:");
expect(workflowText).toContain("npx @kryptosai/mcp-observatory test npx -y @example/mcp-server");
});

it("rejects an invalid --ci-provider value and lists the valid options", async () => {
await expect(initCi({ command: "npx -y server", ciProvider: "jenkins" as never })).rejects.toThrow(
'Invalid --ci-provider "jenkins". Valid options: github-actions, gitlab-ci, circleci, bitbucket-pipelines, azure-pipelines.',
);
});

it("validates --ci-provider in doctor mode too", async () => {
await expect(doctorSetupCi({ ciProvider: "travis" as never })).rejects.toThrow('Invalid --ci-provider "travis"');
});

it("derives the default workflow path from --ci-provider when --workflow is omitted", async () => {
const dir = await tempDir();
const originalCwd = process.cwd();
process.chdir(dir);
try {
const result = await initCi({ command: "npx -y @example/mcp-server", ciProvider: "gitlab-ci" });
expect(result.workflowPath).toBe(".gitlab-ci.yml");
expect(result.workflowStatus).toBe("created");
const workflowText = await readFile(path.join(dir, ".gitlab-ci.yml"), "utf8");
expect(workflowText).toContain("mcp-observatory:");
} finally {
process.chdir(originalCwd);
}
});
});