mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
feat: Refactor feature export service with type guards and parallel conflict checking
This commit is contained in:
@@ -158,45 +158,40 @@ export function createConflictCheckHandler(featureLoader: FeatureLoader) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract features from the data
|
||||
type FeatureExportType = { feature: { id: string; title?: string } };
|
||||
type BulkExportType = { features: FeatureExportType[] };
|
||||
type RawFeatureType = { id: string; title?: string };
|
||||
|
||||
// Extract features from the data using type guards
|
||||
let featuresToCheck: Array<{ id: string; title?: string }> = [];
|
||||
|
||||
if ('features' in parsed && Array.isArray((parsed as BulkExportType).features)) {
|
||||
if (exportService.isBulkExport(parsed)) {
|
||||
// Bulk export format
|
||||
featuresToCheck = (parsed as BulkExportType).features.map((f) => ({
|
||||
featuresToCheck = parsed.features.map((f) => ({
|
||||
id: f.feature.id,
|
||||
title: f.feature.title,
|
||||
}));
|
||||
} else if ('feature' in parsed) {
|
||||
} else if (exportService.isFeatureExport(parsed)) {
|
||||
// Single FeatureExport format
|
||||
const featureExport = parsed as FeatureExportType;
|
||||
featuresToCheck = [
|
||||
{
|
||||
id: featureExport.feature.id,
|
||||
title: featureExport.feature.title,
|
||||
id: parsed.feature.id,
|
||||
title: parsed.feature.title,
|
||||
},
|
||||
];
|
||||
} else if ('id' in parsed) {
|
||||
} else if (exportService.isRawFeature(parsed)) {
|
||||
// Raw Feature format
|
||||
const rawFeature = parsed as RawFeatureType;
|
||||
featuresToCheck = [{ id: rawFeature.id, title: rawFeature.title }];
|
||||
featuresToCheck = [{ id: parsed.id, title: parsed.title }];
|
||||
}
|
||||
|
||||
// Check each feature for conflicts
|
||||
const conflicts: ConflictInfo[] = [];
|
||||
for (const feature of featuresToCheck) {
|
||||
const existing = await featureLoader.get(projectPath, feature.id);
|
||||
conflicts.push({
|
||||
featureId: feature.id,
|
||||
title: feature.title,
|
||||
existingTitle: existing?.title,
|
||||
hasConflict: !!existing,
|
||||
});
|
||||
}
|
||||
// Check each feature for conflicts in parallel
|
||||
const conflicts: ConflictInfo[] = await Promise.all(
|
||||
featuresToCheck.map(async (feature) => {
|
||||
const existing = await featureLoader.get(projectPath, feature.id);
|
||||
return {
|
||||
featureId: feature.id,
|
||||
title: feature.title,
|
||||
existingTitle: existing?.title,
|
||||
hasConflict: !!existing,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const hasConflicts = conflicts.some((c) => c.hasConflict);
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ export class FeatureExportService {
|
||||
...(metadata ? { metadata } : {}),
|
||||
};
|
||||
|
||||
return this.serializeExport(exportData, format, prettyPrint);
|
||||
return this.serialize(exportData, format, prettyPrint);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,16 +170,19 @@ export class FeatureExportService {
|
||||
features = features.filter((f) => f.status === status);
|
||||
}
|
||||
|
||||
// Generate timestamp once for consistent export time across all features
|
||||
const exportedAt = new Date().toISOString();
|
||||
|
||||
// Prepare feature exports
|
||||
const featureExports: FeatureExport[] = features.map((feature) => ({
|
||||
version: FEATURE_EXPORT_VERSION,
|
||||
feature: this.prepareFeatureForExport(feature, { includeHistory, includePlanSpec }),
|
||||
exportedAt: new Date().toISOString(),
|
||||
exportedAt,
|
||||
}));
|
||||
|
||||
const bulkExport: BulkExportResult = {
|
||||
version: FEATURE_EXPORT_VERSION,
|
||||
exportedAt: new Date().toISOString(),
|
||||
exportedAt,
|
||||
count: featureExports.length,
|
||||
features: featureExports,
|
||||
...(metadata ? { metadata } : {}),
|
||||
@@ -187,7 +190,7 @@ export class FeatureExportService {
|
||||
|
||||
logger.info(`Exported ${featureExports.length} features from ${projectPath}`);
|
||||
|
||||
return this.serializeBulkExport(bulkExport, format, prettyPrint);
|
||||
return this.serialize(bulkExport, format, prettyPrint);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -449,7 +452,7 @@ export class FeatureExportService {
|
||||
/**
|
||||
* Check if parsed data is a bulk export
|
||||
*/
|
||||
private isBulkExport(data: unknown): data is BulkExportResult {
|
||||
isBulkExport(data: unknown): data is BulkExportResult {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return false;
|
||||
}
|
||||
@@ -457,6 +460,36 @@ export class FeatureExportService {
|
||||
return 'version' in obj && 'features' in obj && Array.isArray(obj.features);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if parsed data is a single FeatureExport
|
||||
*/
|
||||
isFeatureExport(data: unknown): data is FeatureExport {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return false;
|
||||
}
|
||||
const obj = data as Record<string, unknown>;
|
||||
return (
|
||||
'version' in obj &&
|
||||
'feature' in obj &&
|
||||
'exportedAt' in obj &&
|
||||
typeof obj.feature === 'object' &&
|
||||
obj.feature !== null &&
|
||||
'id' in (obj.feature as Record<string, unknown>)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if parsed data is a raw Feature
|
||||
*/
|
||||
isRawFeature(data: unknown): data is Feature {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return false;
|
||||
}
|
||||
const obj = data as Record<string, unknown>;
|
||||
// A raw feature has 'id' but not the 'version' + 'feature' wrapper of FeatureExport
|
||||
return 'id' in obj && !('feature' in obj && 'version' in obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a feature has required fields
|
||||
*/
|
||||
@@ -475,24 +508,10 @@ export class FeatureExportService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize export data to string
|
||||
* Serialize export data to string (handles both single feature and bulk exports)
|
||||
*/
|
||||
private serializeExport(data: FeatureExport, format: ExportFormat, prettyPrint: boolean): string {
|
||||
if (format === 'yaml') {
|
||||
return yamlStringify(data, {
|
||||
indent: 2,
|
||||
lineWidth: 120,
|
||||
});
|
||||
}
|
||||
|
||||
return prettyPrint ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize bulk export data to string
|
||||
*/
|
||||
private serializeBulkExport(
|
||||
data: BulkExportResult,
|
||||
private serialize<T extends FeatureExport | BulkExportResult>(
|
||||
data: T,
|
||||
format: ExportFormat,
|
||||
prettyPrint: boolean
|
||||
): string {
|
||||
|
||||
Reference in New Issue
Block a user