feat(group): run thrift extractor in group sync

This commit is contained in:
liyipeng06 2026-04-28 16:27:44 +08:00
parent 8be61d6681
commit 1aa4d34fcb
2 changed files with 119 additions and 1 deletions

View file

@ -6,6 +6,7 @@ import { readRegistry, type RegistryEntry } from '../../storage/repo-manager.js'
import type { GroupConfig, RepoHandle, RepoSnapshot, StoredContract, CrossLink } from './types.js';
import { HttpRouteExtractor } from './extractors/http-route-extractor.js';
import { GrpcExtractor } from './extractors/grpc-extractor.js';
import { ThriftExtractor } from './extractors/thrift-extractor.js';
import { TopicExtractor } from './extractors/topic-extractor.js';
import { ManifestExtractor } from './extractors/manifest-extractor.js';
import { buildProviderIndex, runExactMatch, runWildcardMatch } from './matching.js';
@ -93,6 +94,7 @@ export async function syncGroup(config: GroupConfig, opts?: SyncOptions): Promis
const resolve = opts?.resolveRepoHandle ?? defaultResolveHandle(entries);
const httpEx = new HttpRouteExtractor();
const grpcEx = new GrpcExtractor();
const thriftEx = new ThriftExtractor();
const topicEx = new TopicExtractor();
dbExecutors = new Map<string, CypherExecutor>();
const openPoolIds: string[] = [];
@ -140,6 +142,17 @@ export async function syncGroup(config: GroupConfig, opts?: SyncOptions): Promis
}
}
if (config.detect.thrift) {
const extracted = await thriftEx.extract(executor, handle.repoPath, handle);
for (const c of extracted) {
autoContracts.push({
...c,
repo: groupPath,
service: assignService(c.symbolRef.filePath, boundaries),
});
}
}
if (config.detect.topics) {
const extracted = await topicEx.extract(executor, handle.repoPath, handle);
for (const c of extracted) {

View file

@ -1,4 +1,4 @@
import { describe, it, expect } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
@ -22,6 +22,7 @@ describe('syncGroup', () => {
detect: {
http: true,
grpc: false,
thrift: false,
topics: false,
shared_libs: false,
embedding_fallback: false,
@ -228,6 +229,7 @@ describe('syncGroup', () => {
detect: {
http: true,
grpc: false,
thrift: false,
topics: false,
shared_libs: false,
embedding_fallback: false,
@ -339,6 +341,109 @@ describe('syncGroup', () => {
expect(result.unmatched).toHaveLength(0);
});
it('extracts thrift contracts during real sync when thrift detection is enabled', async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-sync-thrift-'));
const storageDir = path.join(tmpDir, '.gitnexus');
fs.mkdirSync(path.join(tmpDir, 'services', 'billing', 'idl'), { recursive: true });
fs.mkdirSync(path.join(tmpDir, 'services', 'billing', 'src'), { recursive: true });
fs.mkdirSync(storageDir, { recursive: true });
fs.writeFileSync(path.join(tmpDir, 'services', 'billing', 'package.json'), '{}');
fs.writeFileSync(
path.join(tmpDir, 'services', 'billing', 'src', 'BillingWorkflow.java'),
'package example; class BillingWorkflow {}',
);
fs.writeFileSync(
path.join(tmpDir, 'services', 'billing', 'idl', 'order.thrift'),
`namespace java billing.v1
service OrderService {
PlaceOrderResponse PlaceOrder(1: PlaceOrderRequest request)
}`,
);
const config = makeConfig({ 'services/billing': 'billing-repo' });
config.detect.http = false;
config.detect.thrift = true;
const poolAdapter = await import('../../../src/core/lbug/pool-adapter.js');
const initSpy = vi.spyOn(poolAdapter, 'initLbug').mockResolvedValue(undefined);
const closeSpy = vi.spyOn(poolAdapter, 'closeLbug').mockResolvedValue(undefined);
try {
const result = await syncGroup(config, {
resolveRepoHandle: async (_name, groupPath) => ({
id: 'billing-repo',
path: groupPath,
repoPath: tmpDir,
storagePath: storageDir,
}),
skipWrite: true,
});
expect(result.missingRepos).toHaveLength(0);
expect(result.contracts).toHaveLength(1);
expect(result.contracts[0]).toMatchObject({
contractId: 'thrift::billing.v1.OrderService/PlaceOrder',
type: 'thrift',
role: 'provider',
repo: 'services/billing',
service: 'services/billing',
symbolRef: {
filePath: 'services/billing/idl/order.thrift',
name: 'OrderService.PlaceOrder',
},
});
expect(initSpy).toHaveBeenCalledWith('billing-repo', path.join(storageDir, 'lbug'));
expect(closeSpy).toHaveBeenCalledWith('billing-repo');
} finally {
initSpy.mockRestore();
closeSpy.mockRestore();
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
it('does not extract thrift contracts during real sync when thrift detection is disabled', async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-sync-thrift-off-'));
const storageDir = path.join(tmpDir, '.gitnexus');
fs.mkdirSync(path.join(tmpDir, 'services', 'billing', 'idl'), { recursive: true });
fs.mkdirSync(storageDir, { recursive: true });
fs.writeFileSync(
path.join(tmpDir, 'services', 'billing', 'idl', 'order.thrift'),
`namespace java billing.v1
service OrderService {
PlaceOrderResponse PlaceOrder(1: PlaceOrderRequest request)
}`,
);
const config = makeConfig({ 'services/billing': 'billing-repo' });
config.detect.http = false;
config.detect.thrift = false;
const poolAdapter = await import('../../../src/core/lbug/pool-adapter.js');
const initSpy = vi.spyOn(poolAdapter, 'initLbug').mockResolvedValue(undefined);
const closeSpy = vi.spyOn(poolAdapter, 'closeLbug').mockResolvedValue(undefined);
try {
const result = await syncGroup(config, {
resolveRepoHandle: async (_name, groupPath) => ({
id: 'billing-repo',
path: groupPath,
repoPath: tmpDir,
storagePath: storageDir,
}),
skipWrite: true,
});
expect(result.missingRepos).toHaveLength(0);
expect(result.contracts).toHaveLength(0);
} finally {
initSpy.mockRestore();
closeSpy.mockRestore();
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
it('dedupes duplicate wildcard cross-links during sync', async () => {
const config = makeConfig({ 'app/provider': 'provider-repo', 'app/consumer': 'consumer-repo' });
const provider: StoredContract = {