@memberjunction/query-gen 0.0.1 → 2.126.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +34 -0
- package/COORDINATOR.md +768 -0
- package/IMPLEMENTATION_PLAN.md +1753 -0
- package/LLM_ENTITY_GROUPING_PLAN.md +977 -0
- package/README.md +675 -29
- package/dist/cli/commands/export.d.ts +15 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +178 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +19 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +282 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +17 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +193 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/config.d.ts +51 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +142 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +57 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/EntityGrouper.d.ts +74 -0
- package/dist/core/EntityGrouper.d.ts.map +1 -0
- package/dist/core/EntityGrouper.js +246 -0
- package/dist/core/EntityGrouper.js.map +1 -0
- package/dist/core/MetadataExporter.d.ts +59 -0
- package/dist/core/MetadataExporter.d.ts.map +1 -0
- package/dist/core/MetadataExporter.js +151 -0
- package/dist/core/MetadataExporter.js.map +1 -0
- package/dist/core/QueryDatabaseWriter.d.ts +50 -0
- package/dist/core/QueryDatabaseWriter.d.ts.map +1 -0
- package/dist/core/QueryDatabaseWriter.js +152 -0
- package/dist/core/QueryDatabaseWriter.js.map +1 -0
- package/dist/core/QueryFixer.d.ts +48 -0
- package/dist/core/QueryFixer.d.ts.map +1 -0
- package/dist/core/QueryFixer.js +115 -0
- package/dist/core/QueryFixer.js.map +1 -0
- package/dist/core/QueryRefiner.d.ts +94 -0
- package/dist/core/QueryRefiner.d.ts.map +1 -0
- package/dist/core/QueryRefiner.js +267 -0
- package/dist/core/QueryRefiner.js.map +1 -0
- package/dist/core/QueryTester.d.ts +70 -0
- package/dist/core/QueryTester.d.ts.map +1 -0
- package/dist/core/QueryTester.js +243 -0
- package/dist/core/QueryTester.js.map +1 -0
- package/dist/core/QueryWriter.d.ts +57 -0
- package/dist/core/QueryWriter.d.ts.map +1 -0
- package/dist/core/QueryWriter.js +184 -0
- package/dist/core/QueryWriter.js.map +1 -0
- package/dist/core/QuestionGenerator.d.ts +58 -0
- package/dist/core/QuestionGenerator.d.ts.map +1 -0
- package/dist/core/QuestionGenerator.js +145 -0
- package/dist/core/QuestionGenerator.js.map +1 -0
- package/dist/data/schema.d.ts +230 -0
- package/dist/data/schema.d.ts.map +1 -0
- package/dist/data/schema.js +6 -0
- package/dist/data/schema.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/PromptNames.d.ts +32 -0
- package/dist/prompts/PromptNames.d.ts.map +1 -0
- package/dist/prompts/PromptNames.js +35 -0
- package/dist/prompts/PromptNames.js.map +1 -0
- package/dist/utils/category-builder.d.ts +28 -0
- package/dist/utils/category-builder.d.ts.map +1 -0
- package/dist/utils/category-builder.js +90 -0
- package/dist/utils/category-builder.js.map +1 -0
- package/dist/utils/entity-helpers.d.ts +49 -0
- package/dist/utils/entity-helpers.d.ts.map +1 -0
- package/dist/utils/entity-helpers.js +189 -0
- package/dist/utils/entity-helpers.js.map +1 -0
- package/dist/utils/error-handlers.d.ts +19 -0
- package/dist/utils/error-handlers.d.ts.map +1 -0
- package/dist/utils/error-handlers.js +41 -0
- package/dist/utils/error-handlers.js.map +1 -0
- package/dist/utils/graph-helpers.d.ts +51 -0
- package/dist/utils/graph-helpers.d.ts.map +1 -0
- package/dist/utils/graph-helpers.js +82 -0
- package/dist/utils/graph-helpers.js.map +1 -0
- package/dist/utils/prompt-helpers.d.ts +25 -0
- package/dist/utils/prompt-helpers.d.ts.map +1 -0
- package/dist/utils/prompt-helpers.js +66 -0
- package/dist/utils/prompt-helpers.js.map +1 -0
- package/dist/utils/query-helpers.d.ts +23 -0
- package/dist/utils/query-helpers.d.ts.map +1 -0
- package/dist/utils/query-helpers.js +34 -0
- package/dist/utils/query-helpers.js.map +1 -0
- package/dist/utils/user-helpers.d.ts +15 -0
- package/dist/utils/user-helpers.d.ts.map +1 -0
- package/dist/utils/user-helpers.js +32 -0
- package/dist/utils/user-helpers.js.map +1 -0
- package/dist/vectors/EmbeddingService.d.ts +58 -0
- package/dist/vectors/EmbeddingService.d.ts.map +1 -0
- package/dist/vectors/EmbeddingService.js +90 -0
- package/dist/vectors/EmbeddingService.js.map +1 -0
- package/dist/vectors/SimilaritySearch.d.ts +51 -0
- package/dist/vectors/SimilaritySearch.d.ts.map +1 -0
- package/dist/vectors/SimilaritySearch.js +85 -0
- package/dist/vectors/SimilaritySearch.js.map +1 -0
- package/docs/API.md +1040 -0
- package/docs/ARCHITECTURE.md +1120 -0
- package/examples/advanced-usage.ts +401 -0
- package/examples/basic-usage.ts +285 -0
- package/package.json +48 -6
- package/src/cli/commands/export.ts +173 -0
- package/src/cli/commands/generate.ts +330 -0
- package/src/cli/commands/validate.ts +185 -0
- package/src/cli/config.ts +203 -0
- package/src/cli/index.ts +63 -0
- package/src/core/EntityGrouper.ts +318 -0
- package/src/core/MetadataExporter.ts +148 -0
- package/src/core/QueryDatabaseWriter.ts +187 -0
- package/src/core/QueryFixer.ts +153 -0
- package/src/core/QueryRefiner.ts +382 -0
- package/src/core/QueryTester.ts +264 -0
- package/src/core/QueryWriter.ts +239 -0
- package/src/core/QuestionGenerator.ts +199 -0
- package/src/data/golden-queries.json +1371 -0
- package/src/data/schema.ts +252 -0
- package/src/index.ts +49 -0
- package/src/prompts/PromptNames.ts +36 -0
- package/src/utils/category-builder.ts +97 -0
- package/src/utils/entity-helpers.ts +203 -0
- package/src/utils/error-handlers.ts +41 -0
- package/src/utils/graph-helpers.ts +99 -0
- package/src/utils/prompt-helpers.ts +79 -0
- package/src/utils/query-helpers.ts +32 -0
- package/src/utils/user-helpers.ts +39 -0
- package/src/vectors/EmbeddingService.ts +109 -0
- package/src/vectors/SimilaritySearch.ts +108 -0
- package/tsconfig.json +39 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate command - Validate existing query templates
|
|
3
|
+
*
|
|
4
|
+
* Tests existing query metadata files to ensure they are valid:
|
|
5
|
+
* - SQL syntax validation
|
|
6
|
+
* - Parameter validation
|
|
7
|
+
* - Output field validation
|
|
8
|
+
* - Execution testing (optional)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import { Metadata, DatabaseProviderBase } from '@memberjunction/core';
|
|
16
|
+
import { getSystemUser } from '../../utils/user-helpers';
|
|
17
|
+
import { QueryTester } from '../../core/QueryTester';
|
|
18
|
+
import { extractErrorMessage } from '../../utils/error-handlers';
|
|
19
|
+
import { GeneratedQuery, QueryMetadataRecord } from '../../data/schema';
|
|
20
|
+
import { loadConfig } from '../config';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Execute the validate command
|
|
24
|
+
*
|
|
25
|
+
* Loads query metadata files and validates each query template.
|
|
26
|
+
* Reports success/failure statistics.
|
|
27
|
+
*/
|
|
28
|
+
export async function validateCommand(options: Record<string, unknown>): Promise<void> {
|
|
29
|
+
const spinner = ora('Initializing validation...').start();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const queryPath = String(options.path || './metadata/queries');
|
|
33
|
+
const verbose = Boolean(options.verbose);
|
|
34
|
+
|
|
35
|
+
// Load configuration
|
|
36
|
+
const config = loadConfig(options);
|
|
37
|
+
|
|
38
|
+
// 1. Get system user from UserCache (populated by provider initialization)
|
|
39
|
+
const contextUser = getSystemUser();
|
|
40
|
+
|
|
41
|
+
// 2. Verify database connection and load metadata
|
|
42
|
+
spinner.text = 'Loading metadata...';
|
|
43
|
+
// Assume provider is already configured by the calling application
|
|
44
|
+
if (!Metadata.Provider) {
|
|
45
|
+
throw new Error('Metadata provider not configured. Please ensure database connection is set up before running CLI.');
|
|
46
|
+
}
|
|
47
|
+
spinner.succeed('Metadata loaded');
|
|
48
|
+
|
|
49
|
+
// 3. Load query metadata files
|
|
50
|
+
spinner.start(`Loading query files from ${queryPath}...`);
|
|
51
|
+
const queryFiles = await loadQueryFiles(queryPath);
|
|
52
|
+
spinner.succeed(chalk.green(`Found ${queryFiles.length} query files`));
|
|
53
|
+
|
|
54
|
+
// 4. Validate each query
|
|
55
|
+
const dataProvider = Metadata.Provider.DatabaseConnection as DatabaseProviderBase;
|
|
56
|
+
let passCount = 0;
|
|
57
|
+
let failCount = 0;
|
|
58
|
+
const errors: Array<{ file: string; error: string }> = [];
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < queryFiles.length; i++) {
|
|
61
|
+
const { file, queries } = queryFiles[i];
|
|
62
|
+
const filePrefix = chalk.cyan(`[${i + 1}/${queryFiles.length}]`);
|
|
63
|
+
|
|
64
|
+
spinner.start(`${filePrefix} Validating ${chalk.dim(file)}...`);
|
|
65
|
+
|
|
66
|
+
for (const queryRecord of queries) {
|
|
67
|
+
try {
|
|
68
|
+
const query = convertMetadataToGeneratedQuery(queryRecord);
|
|
69
|
+
|
|
70
|
+
// Create a minimal business question and entity metadata for testing
|
|
71
|
+
const dummyQuestion = {
|
|
72
|
+
userQuestion: queryRecord.fields.UserQuestion || 'Test query',
|
|
73
|
+
description: queryRecord.fields.Description || '',
|
|
74
|
+
technicalDescription: queryRecord.fields.TechnicalDescription || '',
|
|
75
|
+
complexity: 'medium' as const,
|
|
76
|
+
requiresAggregation: false,
|
|
77
|
+
requiresJoins: false,
|
|
78
|
+
entities: []
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const tester = new QueryTester(dataProvider, [], dummyQuestion, contextUser, config);
|
|
82
|
+
|
|
83
|
+
// Test query execution
|
|
84
|
+
const testResult = await tester.testQuery(query, 1);
|
|
85
|
+
|
|
86
|
+
if (testResult.success) {
|
|
87
|
+
passCount++;
|
|
88
|
+
if (verbose) {
|
|
89
|
+
spinner.info(`${filePrefix} ${chalk.green('✓')} ${queryRecord.fields.Name}`);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
failCount++;
|
|
93
|
+
const errorMsg = testResult.error || 'Unknown error';
|
|
94
|
+
errors.push({ file, error: `${queryRecord.fields.Name}: ${errorMsg}` });
|
|
95
|
+
if (verbose) {
|
|
96
|
+
spinner.warn(`${filePrefix} ${chalk.red('✗')} ${queryRecord.fields.Name}: ${errorMsg}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch (error: unknown) {
|
|
100
|
+
failCount++;
|
|
101
|
+
const errorMsg = extractErrorMessage(error, 'Query Validation');
|
|
102
|
+
errors.push({ file, error: `${queryRecord.fields.Name}: ${errorMsg}` });
|
|
103
|
+
if (verbose) {
|
|
104
|
+
spinner.warn(`${filePrefix} ${chalk.red('✗')} ${queryRecord.fields.Name}: ${errorMsg}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
spinner.succeed(`${filePrefix} ${chalk.dim(file)} complete`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 5. Summary
|
|
113
|
+
if (failCount === 0) {
|
|
114
|
+
spinner.succeed(chalk.green.bold(`✓ All ${passCount} queries validated successfully!`));
|
|
115
|
+
console.log('\n' + chalk.green.bold('✓ Validation complete!\n'));
|
|
116
|
+
console.log(chalk.bold('Summary:'));
|
|
117
|
+
console.log(` Total Queries: ${chalk.cyan(passCount.toString())}`);
|
|
118
|
+
console.log(` Passed: ${chalk.green(passCount.toString())}`);
|
|
119
|
+
console.log(` Failed: ${chalk.green('0')}`);
|
|
120
|
+
process.exit(0);
|
|
121
|
+
} else {
|
|
122
|
+
spinner.fail(chalk.yellow(`Validation completed with ${failCount} errors`));
|
|
123
|
+
console.log('\n' + chalk.yellow.bold('⚠ Validation completed with errors\n'));
|
|
124
|
+
console.log(chalk.bold('Summary:'));
|
|
125
|
+
console.log(` Total Queries: ${chalk.cyan((passCount + failCount).toString())}`);
|
|
126
|
+
console.log(` Passed: ${chalk.green(passCount.toString())}`);
|
|
127
|
+
console.log(` Failed: ${chalk.red(failCount.toString())}`);
|
|
128
|
+
|
|
129
|
+
if (errors.length > 0) {
|
|
130
|
+
console.log('\n' + chalk.bold('Errors:'));
|
|
131
|
+
for (const { file, error } of errors) {
|
|
132
|
+
console.log(chalk.red(` ${file}: ${error}`));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
} catch (error: unknown) {
|
|
139
|
+
spinner.fail(chalk.red('Validation failed'));
|
|
140
|
+
console.error(chalk.red(extractErrorMessage(error, 'Query Validation')));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Load all query metadata files from the specified directory
|
|
147
|
+
*/
|
|
148
|
+
async function loadQueryFiles(queryPath: string): Promise<Array<{ file: string; queries: QueryMetadataRecord[] }>> {
|
|
149
|
+
const files: Array<{ file: string; queries: QueryMetadataRecord[] }> = [];
|
|
150
|
+
|
|
151
|
+
if (!fs.existsSync(queryPath)) {
|
|
152
|
+
throw new Error(`Query path not found: ${queryPath}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const entries = fs.readdirSync(queryPath);
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
const fullPath = path.join(queryPath, entry);
|
|
158
|
+
const stat = fs.statSync(fullPath);
|
|
159
|
+
|
|
160
|
+
if (stat.isFile() && entry.endsWith('.json')) {
|
|
161
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
162
|
+
const data = JSON.parse(content);
|
|
163
|
+
|
|
164
|
+
// Handle both single query and array formats
|
|
165
|
+
const queries = Array.isArray(data) ? data : [data];
|
|
166
|
+
files.push({ file: entry, queries });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return files;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Convert metadata record to GeneratedQuery format for testing
|
|
175
|
+
*
|
|
176
|
+
* Note: QueryFields and QueryParameters are auto-extracted by QueryEntity.server.ts,
|
|
177
|
+
* so we only validate the SQL itself. The validate command focuses on SQL syntax
|
|
178
|
+
* and execution, not on field/parameter metadata which is managed by MJ.
|
|
179
|
+
*/
|
|
180
|
+
function convertMetadataToGeneratedQuery(record: QueryMetadataRecord): GeneratedQuery {
|
|
181
|
+
return {
|
|
182
|
+
sql: record.fields.SQL,
|
|
183
|
+
parameters: [], // Not needed for validation - QueryEntity will extract
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader for QueryGen
|
|
3
|
+
*
|
|
4
|
+
* Loads configuration from mj.config.cjs and merges with CLI options
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* QueryGen configuration options
|
|
9
|
+
*/
|
|
10
|
+
export interface QueryGenConfig {
|
|
11
|
+
// Entity Filtering
|
|
12
|
+
// NOTE: includeEntities and excludeEntities are mutually exclusive
|
|
13
|
+
// - includeEntities: If provided, ONLY these entities will be processed (allowlist)
|
|
14
|
+
// - excludeEntities: If provided, these entities will be excluded from processing (denylist)
|
|
15
|
+
// - If both are provided, includeEntities takes precedence and excludeEntities is ignored
|
|
16
|
+
includeEntities: string[];
|
|
17
|
+
excludeEntities: string[];
|
|
18
|
+
excludeSchemas: string[];
|
|
19
|
+
|
|
20
|
+
// Entity Grouping
|
|
21
|
+
questionsPerGroup: number;
|
|
22
|
+
minGroupSize: number; // Minimum entities per group
|
|
23
|
+
maxGroupSize: number; // Maximum entities per group
|
|
24
|
+
|
|
25
|
+
// AI Configuration
|
|
26
|
+
modelOverride?: string; // Override model for all prompts (e.g., "GPT-OSS-120B")
|
|
27
|
+
vendorOverride?: string; // Override vendor for all prompts (e.g., "Groq")
|
|
28
|
+
embeddingModel: string;
|
|
29
|
+
|
|
30
|
+
// Iteration Limits
|
|
31
|
+
maxRefinementIterations: number;
|
|
32
|
+
maxFixingIterations: number;
|
|
33
|
+
|
|
34
|
+
// Few-Shot Learning
|
|
35
|
+
topSimilarQueries: number;
|
|
36
|
+
|
|
37
|
+
// Similarity Weighting
|
|
38
|
+
similarityWeights: {
|
|
39
|
+
userQuestion: number;
|
|
40
|
+
description: number;
|
|
41
|
+
technicalDescription: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Output Configuration
|
|
45
|
+
outputMode: 'metadata' | 'database' | 'both';
|
|
46
|
+
outputDirectory: string;
|
|
47
|
+
outputCategoryDirectory?: string; // Optional: defaults to outputDirectory if not provided
|
|
48
|
+
|
|
49
|
+
// Query Category Configuration
|
|
50
|
+
rootQueryCategory: string;
|
|
51
|
+
autoCreateEntityQueryCategories: boolean;
|
|
52
|
+
|
|
53
|
+
// Performance
|
|
54
|
+
parallelGenerations: number;
|
|
55
|
+
enableCaching: boolean;
|
|
56
|
+
|
|
57
|
+
// Validation
|
|
58
|
+
testWithSampleData: boolean;
|
|
59
|
+
requireMinRows: number;
|
|
60
|
+
maxRefinementRows: number;
|
|
61
|
+
|
|
62
|
+
// Verbose Logging
|
|
63
|
+
verbose: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Default configuration values
|
|
68
|
+
*/
|
|
69
|
+
const DEFAULT_CONFIG: QueryGenConfig = {
|
|
70
|
+
includeEntities: [],
|
|
71
|
+
excludeEntities: [],
|
|
72
|
+
excludeSchemas: ['sys', 'INFORMATION_SCHEMA', '__mj'],
|
|
73
|
+
questionsPerGroup: 2,
|
|
74
|
+
minGroupSize: 2, // Multi-entity groups have at least 2 entities
|
|
75
|
+
maxGroupSize: 3, // Keep groups small for focused questions
|
|
76
|
+
embeddingModel: 'text-embedding-3-small',
|
|
77
|
+
maxRefinementIterations: 3,
|
|
78
|
+
maxFixingIterations: 5,
|
|
79
|
+
topSimilarQueries: 5,
|
|
80
|
+
similarityWeights: {
|
|
81
|
+
userQuestion: 0.2,
|
|
82
|
+
description: 0.40,
|
|
83
|
+
technicalDescription: 0.40,
|
|
84
|
+
},
|
|
85
|
+
outputMode: 'metadata',
|
|
86
|
+
outputDirectory: './metadata/queries',
|
|
87
|
+
rootQueryCategory: 'Auto-Generated',
|
|
88
|
+
autoCreateEntityQueryCategories: false,
|
|
89
|
+
parallelGenerations: 1,
|
|
90
|
+
enableCaching: true,
|
|
91
|
+
testWithSampleData: true,
|
|
92
|
+
requireMinRows: 0,
|
|
93
|
+
maxRefinementRows: 10,
|
|
94
|
+
verbose: false,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Load configuration from mj.config.cjs and merge with CLI options
|
|
99
|
+
*
|
|
100
|
+
* Configuration priority (highest to lowest):
|
|
101
|
+
* 1. CLI options (command line flags)
|
|
102
|
+
* 2. mj.config.cjs queryGen section
|
|
103
|
+
* 3. Default values
|
|
104
|
+
*
|
|
105
|
+
* @param cliOptions - Options provided via command line
|
|
106
|
+
* @returns Merged configuration ready for use
|
|
107
|
+
*/
|
|
108
|
+
export function loadConfig(cliOptions: Record<string, unknown>): QueryGenConfig {
|
|
109
|
+
// Start with defaults
|
|
110
|
+
const config: QueryGenConfig = { ...DEFAULT_CONFIG };
|
|
111
|
+
|
|
112
|
+
// Load mj.config.cjs if it exists
|
|
113
|
+
const mjConfig = loadMjConfig();
|
|
114
|
+
if (mjConfig && mjConfig.queryGen) {
|
|
115
|
+
Object.assign(config, mjConfig.queryGen);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Override with CLI options
|
|
119
|
+
if (cliOptions.entities) {
|
|
120
|
+
config.includeEntities = parseArrayOption(cliOptions.entities);
|
|
121
|
+
}
|
|
122
|
+
if (cliOptions.excludeEntities) {
|
|
123
|
+
config.excludeEntities = parseArrayOption(cliOptions.excludeEntities);
|
|
124
|
+
}
|
|
125
|
+
if (cliOptions.excludeSchemas) {
|
|
126
|
+
config.excludeSchemas = parseArrayOption(cliOptions.excludeSchemas);
|
|
127
|
+
}
|
|
128
|
+
if (cliOptions.maxRefinements) {
|
|
129
|
+
config.maxRefinementIterations = parseNumberOption(cliOptions.maxRefinements, 'maxRefinements');
|
|
130
|
+
}
|
|
131
|
+
if (cliOptions.maxFixes) {
|
|
132
|
+
config.maxFixingIterations = parseNumberOption(cliOptions.maxFixes, 'maxFixes');
|
|
133
|
+
}
|
|
134
|
+
if (cliOptions.model) {
|
|
135
|
+
config.modelOverride = String(cliOptions.model);
|
|
136
|
+
}
|
|
137
|
+
if (cliOptions.vendor) {
|
|
138
|
+
config.vendorOverride = String(cliOptions.vendor);
|
|
139
|
+
}
|
|
140
|
+
if (cliOptions.output) {
|
|
141
|
+
config.outputDirectory = String(cliOptions.output);
|
|
142
|
+
}
|
|
143
|
+
if (cliOptions.mode) {
|
|
144
|
+
const mode = String(cliOptions.mode);
|
|
145
|
+
if (mode === 'metadata' || mode === 'database' || mode === 'both') {
|
|
146
|
+
config.outputMode = mode;
|
|
147
|
+
} else {
|
|
148
|
+
throw new Error(`Invalid output mode: ${mode}. Must be metadata, database, or both`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (cliOptions.verbose) {
|
|
152
|
+
config.verbose = true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Validate entity filtering (includeEntities and excludeEntities are mutually exclusive)
|
|
156
|
+
if (config.includeEntities.length > 0 && config.excludeEntities.length > 0) {
|
|
157
|
+
console.warn('[Warning] Both includeEntities and excludeEntities provided. includeEntities takes precedence, excludeEntities will be ignored.');
|
|
158
|
+
config.excludeEntities = [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return config;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Load mj.config.cjs from current working directory
|
|
166
|
+
* Returns null if file doesn't exist or can't be loaded
|
|
167
|
+
*/
|
|
168
|
+
function loadMjConfig(): { queryGen?: Partial<QueryGenConfig> } | null {
|
|
169
|
+
try {
|
|
170
|
+
const configPath = require('path').join(process.cwd(), 'mj.config.cjs');
|
|
171
|
+
const fs = require('fs');
|
|
172
|
+
if (fs.existsSync(configPath)) {
|
|
173
|
+
return require(configPath);
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
// Config file doesn't exist or couldn't be loaded - not an error
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Parse array option from CLI (handles both comma-separated strings and arrays)
|
|
183
|
+
*/
|
|
184
|
+
function parseArrayOption(value: unknown): string[] {
|
|
185
|
+
if (Array.isArray(value)) {
|
|
186
|
+
return value.map(String);
|
|
187
|
+
}
|
|
188
|
+
if (typeof value === 'string') {
|
|
189
|
+
return value.split(',').map(s => s.trim());
|
|
190
|
+
}
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Parse number option from CLI with validation
|
|
196
|
+
*/
|
|
197
|
+
function parseNumberOption(value: unknown, name: string): number {
|
|
198
|
+
const num = Number(value);
|
|
199
|
+
if (isNaN(num) || num < 0) {
|
|
200
|
+
throw new Error(`Invalid ${name}: must be a positive number`);
|
|
201
|
+
}
|
|
202
|
+
return num;
|
|
203
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for QueryGen package
|
|
4
|
+
*
|
|
5
|
+
* Usage: mj-querygen <command> [options]
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* generate - Generate queries for entities
|
|
9
|
+
* validate - Validate existing query templates
|
|
10
|
+
* export - Export queries from database to metadata files
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Command } from 'commander';
|
|
14
|
+
import { generateCommand } from './commands/generate';
|
|
15
|
+
import { validateCommand } from './commands/validate';
|
|
16
|
+
import { exportCommand } from './commands/export';
|
|
17
|
+
|
|
18
|
+
const packageJson = require('../../package.json');
|
|
19
|
+
|
|
20
|
+
const program = new Command();
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.name('mj-querygen')
|
|
24
|
+
.description('AI-powered SQL query template generation for MemberJunction')
|
|
25
|
+
.version(packageJson.version);
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command('generate')
|
|
29
|
+
.description('Generate queries for entities')
|
|
30
|
+
.option('-e, --entities <names...>', 'Specific entities to generate queries for')
|
|
31
|
+
.option('-x, --exclude-entities <names...>', 'Entities to exclude')
|
|
32
|
+
.option('-s, --exclude-schemas <names...>', 'Schemas to exclude')
|
|
33
|
+
.option('-m, --max-entities <number>', 'Max entities per group', '3')
|
|
34
|
+
.option('-r, --max-refinements <number>', 'Max refinement iterations', '3')
|
|
35
|
+
.option('-f, --max-fixes <number>', 'Max error-fixing attempts', '5')
|
|
36
|
+
.option('--model <name>', 'Preferred AI model')
|
|
37
|
+
.option('--vendor <name>', 'Preferred AI vendor')
|
|
38
|
+
.option('-o, --output <path>', 'Output directory')
|
|
39
|
+
.option('--mode <mode>', 'Output mode: metadata|database|both')
|
|
40
|
+
.option('-v, --verbose', 'Verbose output')
|
|
41
|
+
.action(generateCommand);
|
|
42
|
+
|
|
43
|
+
program
|
|
44
|
+
.command('validate')
|
|
45
|
+
.description('Validate existing query templates')
|
|
46
|
+
.option('-p, --path <path>', 'Path to queries metadata file', './metadata/queries')
|
|
47
|
+
.option('-v, --verbose', 'Verbose output')
|
|
48
|
+
.action(validateCommand);
|
|
49
|
+
|
|
50
|
+
program
|
|
51
|
+
.command('export')
|
|
52
|
+
.description('Export queries from database to metadata files')
|
|
53
|
+
.option('-o, --output <path>', 'Output directory')
|
|
54
|
+
.option('-v, --verbose', 'Verbose output')
|
|
55
|
+
.action(exportCommand);
|
|
56
|
+
|
|
57
|
+
// Parse command line arguments
|
|
58
|
+
program.parse();
|
|
59
|
+
|
|
60
|
+
// Show help if no command provided
|
|
61
|
+
if (!process.argv.slice(2).length) {
|
|
62
|
+
program.outputHelp();
|
|
63
|
+
}
|