mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
fix(ci-setup): resolve all PR CI failures and correctness bugs
- console.error → console.log (ESLint no-console) - Add i18n keys for ci-setup command + all 9 options in en.ts, zh-CN.ts, help-i18n.ts; add ['ci-setup'] to allHelpCommands in cli-index-help test - Container port hardcoded to 4747 (image CMD is not overridable); --port now controls host binding only; fixes dead deployments with non-4747 ports - Guard non-TTY overwrite prompt with process.stdin.isTTY check - Add port range validation (1–65534) with clear error message - Fix ACA --target-port to 4747 (container port, not host) - Add permissions/concurrency/timeout-minutes to generated GHA workflow - Fix GITNEXUS.md false claim about skills being auto-committed - Strip // comments from MCP snippet (invalid JSON); use _comment/_note keys - All 48 unit tests passing; prettier check clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
084510f488
commit
8bc4ce1859
6 changed files with 78 additions and 28 deletions
|
|
@ -35,9 +35,7 @@ export const ciSetupCommand = async (options?: {
|
|||
const detect = await detectEnvironment(cwd);
|
||||
|
||||
if (!detect.gitRoot) {
|
||||
console.error(
|
||||
'✗ Not a git repository. Run `gitnexus ci-setup` from inside a git repo root.',
|
||||
);
|
||||
console.log('✗ Not a git repository. Run `gitnexus ci-setup` from inside a git repo root.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
|
@ -51,14 +49,23 @@ export const ciSetupCommand = async (options?: {
|
|||
if (detect.hasDocker) {
|
||||
console.log(' ✓ Docker: docker-compose or Dockerfile found');
|
||||
}
|
||||
console.log(` ${detect.portAvailable ? '✓' : '⚠'} Port 4747: ${detect.portAvailable ? 'available' : 'in use (serve may already be running)'}`);
|
||||
console.log(
|
||||
` ${detect.portAvailable ? '✓' : '⚠'} Port 4747: ${detect.portAvailable ? 'available' : 'in use (serve may already be running)'}`,
|
||||
);
|
||||
console.log(' ⚠ License: PolyForm-Noncommercial — confirm non-commercial use\n');
|
||||
|
||||
// Parse and validate options from commander flags
|
||||
const partial: Partial<CiSetupOptions> = {};
|
||||
if (options?.ci) partial.ci = options.ci as CiSystem;
|
||||
if (options?.deploy) partial.deploy = options.deploy as DeployTarget;
|
||||
if (options?.port) partial.port = parseInt(options.port, 10);
|
||||
if (options?.port) {
|
||||
const portNum = parseInt(options.port as string, 10);
|
||||
if (isNaN(portNum) || portNum < 1 || portNum > 65534) {
|
||||
console.log(`✗ Invalid port: "${options.port}". Must be an integer between 1 and 65534.`);
|
||||
process.exit(1);
|
||||
}
|
||||
partial.port = portNum;
|
||||
}
|
||||
if (options?.auth) partial.auth = options.auth as AuthMode;
|
||||
if (options?.branchStrategy) partial.branchStrategy = options.branchStrategy as BranchStrategy;
|
||||
if (options?.dryRun !== undefined) partial.dryRun = options.dryRun;
|
||||
|
|
@ -120,13 +127,15 @@ async function applyFiles(
|
|||
|
||||
if (existingContent !== null && !opts.yes) {
|
||||
console.log(`\n⚠ ${file.relativePath} already exists and differs.`);
|
||||
if (!opts.yes) {
|
||||
const { confirm } = await import('@inquirer/prompts');
|
||||
const ok = await confirm({ message: `Overwrite ${file.relativePath}?`, default: false });
|
||||
if (!ok) {
|
||||
result.skipped.push(`${file.relativePath} (skipped by user)`);
|
||||
continue;
|
||||
}
|
||||
if (!process.stdin.isTTY) {
|
||||
result.skipped.push(`${file.relativePath} (differs, non-TTY — use --yes to overwrite)`);
|
||||
continue;
|
||||
}
|
||||
const { confirm } = await import('@inquirer/prompts');
|
||||
const ok = await confirm({ message: `Overwrite ${file.relativePath}?`, default: false });
|
||||
if (!ok) {
|
||||
result.skipped.push(`${file.relativePath} (skipped by user)`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -175,12 +184,16 @@ function printResult(result: CiSetupResult, opts: CiSetupOptions): void {
|
|||
}
|
||||
|
||||
if (opts.ci === 'github-actions' || opts.ci === 'both') {
|
||||
console.log(` ${step++}. Commit .github/workflows/gitnexus-ci.yml and push to trigger the first index.`);
|
||||
console.log(
|
||||
` ${step++}. Commit .github/workflows/gitnexus-ci.yml and push to trigger the first index.`,
|
||||
);
|
||||
}
|
||||
if (opts.ci === 'azure-devops' || opts.ci === 'both') {
|
||||
console.log(` ${step++}. Import azure-pipelines-gitnexus.yml into Azure DevOps and run it.`);
|
||||
}
|
||||
|
||||
console.log(` ${step++}. Follow GITNEXUS.md to connect Claude Code / Cursor to the shared server.`);
|
||||
console.log(
|
||||
` ${step++}. Follow GITNEXUS.md to connect Claude Code / Cursor to the shared server.`,
|
||||
);
|
||||
console.log('');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,18 @@ name: GitNexus Index
|
|||
|
||||
${onBlock}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: gitnexus-$\{{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
gitnexus-index:
|
||||
name: Index repository
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -136,7 +144,7 @@ services:
|
|||
GITNEXUS_HOME: /data/gitnexus
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:${opts.port}/api/health"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:4747/api/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
|
@ -172,7 +180,7 @@ services:
|
|||
gitnexus:
|
||||
image: ghcr.io/abhigyanpatwari/gitnexus:latest
|
||||
ports:
|
||||
- "\${GITNEXUS_PORT:-${opts.port}}:${opts.port}"
|
||||
- "\${GITNEXUS_PORT:-${opts.port}}:4747"
|
||||
volumes:
|
||||
- gitnexus-data:/data/gitnexus
|
||||
- \${WORKSPACE_DIR:-./workspace}:/workspace:ro
|
||||
|
|
@ -180,7 +188,7 @@ services:
|
|||
GITNEXUS_HOME: /data/gitnexus
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:${opts.port}/api/health"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:4747/api/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
|
@ -201,7 +209,7 @@ function buildCaddyfile(opts: CiSetupOptions): string {
|
|||
@authorized header Authorization "Bearer {env.GITNEXUS_TOKEN}"
|
||||
|
||||
handle @authorized {
|
||||
reverse_proxy gitnexus:${opts.port}
|
||||
reverse_proxy gitnexus:4747
|
||||
}
|
||||
|
||||
respond "Unauthorized" 401
|
||||
|
|
@ -277,7 +285,7 @@ az containerapp create \\
|
|||
--resource-group "\$RESOURCE_GROUP" \\
|
||||
--environment "\$ENVIRONMENT" \\
|
||||
--image ghcr.io/abhigyanpatwari/gitnexus:latest \\
|
||||
--target-port ${opts.port} \\
|
||||
--target-port 4747 \\
|
||||
--ingress internal \\
|
||||
--env-vars "GITNEXUS_HOME=/data/gitnexus" \\
|
||||
--volume-name gitnexus-data \\
|
||||
|
|
@ -308,15 +316,10 @@ function buildMcpSnippet(opts: CiSetupOptions): string {
|
|||
}`
|
||||
: '';
|
||||
|
||||
const commentLine = urlComment.replace(/^\s*\/\/\s*/, '').trim();
|
||||
return `{
|
||||
// GitNexus shared MCP server snippet — merge into ~/.claude/settings.json mcpServers block.
|
||||
// Do NOT auto-apply: use 'gitnexus setup' for personal stdio MCP; this snippet is for the
|
||||
// shared HTTP server variant.
|
||||
//
|
||||
// NOTE: Verify the exact Claude Code HTTP MCP entry format against current docs
|
||||
// (https://github.com/anthropics/claude-code) before applying — field names may evolve.
|
||||
//
|
||||
${urlComment}
|
||||
"_comment": "${commentLine}",
|
||||
"_note": "Merge into ~/.claude/settings.json mcpServers block. Verify the exact Claude Code HTTP MCP entry format against current docs before applying.",
|
||||
"mcpServers": {
|
||||
"gitnexus": {
|
||||
"type": "http",
|
||||
|
|
@ -449,7 +452,7 @@ When the CI workflow runs \`analyze --skills\`, GitNexus generates repo-specific
|
|||
- \`.claude/skills/gitnexus/\` — standard GitNexus MCP skills (query, impact, context, detect-changes)
|
||||
- \`.claude/skills/generated/\` — community-detected area skills (one file per functional cluster)
|
||||
|
||||
These are committed to the repository so every developer's Claude Code session has them available automatically.
|
||||
These are written into the repository by the CI workflow. To share them with your team, add a \`git add .claude/skills/ && git commit\` step after \`analyze --skills\` in your workflow, or commit them manually after the first run.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const TITLE_KEYS = {
|
|||
const COMMAND_DESCRIPTION_KEYS = {
|
||||
'': 'help.description.root',
|
||||
setup: 'help.command.setup.description',
|
||||
'ci-setup': 'help.command.ciSetup.description',
|
||||
analyze: 'help.command.analyze.description',
|
||||
index: 'help.command.index.description',
|
||||
serve: 'help.command.serve.description',
|
||||
|
|
@ -44,6 +45,15 @@ const COMMAND_DESCRIPTION_KEYS = {
|
|||
|
||||
const OPTION_DESCRIPTION_KEYS = {
|
||||
'|-V, --version': 'help.option.version',
|
||||
'ci-setup|--ci <system>': 'help.option.ciSetup.ci',
|
||||
'ci-setup|--deploy <target>': 'help.option.ciSetup.deploy',
|
||||
'ci-setup|--port <port>': 'help.option.ciSetup.port',
|
||||
'ci-setup|--auth <mode>': 'help.option.ciSetup.auth',
|
||||
'ci-setup|--branch-strategy <strategy>': 'help.option.ciSetup.branchStrategy',
|
||||
'ci-setup|--dry-run': 'help.option.ciSetup.dryRun',
|
||||
'ci-setup|--apply': 'help.option.ciSetup.apply',
|
||||
'ci-setup|--yes': 'help.option.ciSetup.yes',
|
||||
'ci-setup|--output-dir <path>': 'help.option.ciSetup.outputDir',
|
||||
'analyze|-f, --force': 'help.option.analyze.force',
|
||||
'analyze|--repair-fts': 'help.option.analyze.repairFts',
|
||||
'analyze|--embeddings [limit]': 'help.option.analyze.embeddings',
|
||||
|
|
|
|||
|
|
@ -106,6 +106,8 @@ export const en = {
|
|||
'help.option.version': 'output the version number',
|
||||
'help.command.setup.description':
|
||||
'One-time setup: configure MCP for Cursor, Claude Code, OpenCode, Codex',
|
||||
'help.command.ciSetup.description':
|
||||
'Generate CI/CD workflows, Docker Compose, and MCP config for a shared team GitNexus server',
|
||||
'help.command.analyze.description': 'Index a repository (full analysis)',
|
||||
'help.command.index.description':
|
||||
'Register an existing .gitnexus/ folder into the global registry (no re-analysis needed)',
|
||||
|
|
@ -146,6 +148,16 @@ export const en = {
|
|||
'Cross-repo impact for a symbol in one member repo of a group',
|
||||
'help.command.group.query.description': 'Search execution flows across all repos in a group',
|
||||
'help.command.group.contracts.description': 'Inspect Contract Registry',
|
||||
'help.option.ciSetup.ci': 'CI/CD system: github-actions, azure-devops, or both',
|
||||
'help.option.ciSetup.deploy': 'Deploy target: docker, azure-container-app, or both',
|
||||
'help.option.ciSetup.port': 'Host port to bind (container always runs on 4747)',
|
||||
'help.option.ciSetup.auth': 'Auth mode: token (Caddy proxy) or none',
|
||||
'help.option.ciSetup.branchStrategy': 'Index strategy: pr-scoped or main-only',
|
||||
'help.option.ciSetup.dryRun':
|
||||
'Print generated files without writing (default when no mode flag given)',
|
||||
'help.option.ciSetup.apply': 'Write files with per-file confirmation gates',
|
||||
'help.option.ciSetup.yes': 'Skip per-file confirmation prompts (use with --apply)',
|
||||
'help.option.ciSetup.outputDir': 'Directory to write generated files (default: git root)',
|
||||
'help.option.analyze.force': 'Force full re-index even if up to date',
|
||||
'help.option.analyze.repairFts': 'Repair/rebuild search FTS indexes without full re-analysis',
|
||||
'help.option.analyze.embeddings':
|
||||
|
|
|
|||
|
|
@ -108,6 +108,8 @@ export const zhCN = {
|
|||
'help.option.help': '显示命令帮助',
|
||||
'help.option.version': '输出版本号',
|
||||
'help.command.setup.description': '一次性设置:为 Cursor、Claude Code、OpenCode、Codex 配置 MCP',
|
||||
'help.command.ciSetup.description':
|
||||
'为团队共享 GitNexus 服务器生成 CI/CD 工作流、Docker Compose 和 MCP 配置',
|
||||
'help.command.analyze.description': '索引仓库(完整分析)',
|
||||
'help.command.index.description': '将现有 .gitnexus/ 文件夹注册到全局注册表(无需重新分析)',
|
||||
'help.command.serve.description': '启动供 Web UI 连接的本地 HTTP 服务器',
|
||||
|
|
@ -139,6 +141,15 @@ export const zhCN = {
|
|||
'help.command.group.impact.description': '分析仓库组中某个成员仓库符号的跨仓库影响',
|
||||
'help.command.group.query.description': '跨仓库组所有仓库搜索执行流程',
|
||||
'help.command.group.contracts.description': '查看 Contract Registry',
|
||||
'help.option.ciSetup.ci': 'CI/CD 系统:github-actions、azure-devops 或 both',
|
||||
'help.option.ciSetup.deploy': '部署目标:docker、azure-container-app 或 both',
|
||||
'help.option.ciSetup.port': '绑定的主机端口(容器始终运行在 4747)',
|
||||
'help.option.ciSetup.auth': '认证模式:token(Caddy 代理)或 none',
|
||||
'help.option.ciSetup.branchStrategy': '索引策略:pr-scoped 或 main-only',
|
||||
'help.option.ciSetup.dryRun': '打印生成的文件而不写入(未指定模式标志时的默认行为)',
|
||||
'help.option.ciSetup.apply': '写入文件,每个文件有独立确认步骤',
|
||||
'help.option.ciSetup.yes': '跳过逐文件确认提示(与 --apply 配合使用)',
|
||||
'help.option.ciSetup.outputDir': '生成文件的写入目录(默认:git 根目录)',
|
||||
'help.option.analyze.force': '即使已是最新也强制完整重建索引',
|
||||
'help.option.analyze.repairFts': '修复/重建搜索 FTS 索引,不执行完整重新分析',
|
||||
'help.option.analyze.embeddings':
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ function runRootHelp(env: NodeJS.ProcessEnv = {}) {
|
|||
const allHelpCommands = [
|
||||
[],
|
||||
['setup'],
|
||||
['ci-setup'],
|
||||
['analyze'],
|
||||
['index'],
|
||||
['serve'],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue