import fs from 'fs'; import path from 'path'; import Fuse from 'fuse.js'; import inquirer from 'inquirer'; import { parse, stringify } from 'yaml'; import { globSync } from 'glob'; import { getProjectPath, getProjectOutput } from './cluserHelper'; import { IConnection } from '../commands/profile/generators/connections/connectors'; import { Dictionary } from 'tsyringe/dist/typings/types'; import { objectAssociationYaml } from '../components/objectAssociationTemplate'; import { v4 } from 'uuid'; import { tableYaml } from '../components/tableTemplate'; import { objectYaml } from '../components/objectTemplate'; import chalk from 'chalk'; import { readVariableValue } from './variablesHelper'; import { IExternalReplacement } from './@types/IExternalReplacement'; export const PULLER_POSTFIX = 'Puller'; export const OBJECT_POSTFIX = 'Task'; export const PUSHER_POSTFIX = 'Pusher'; export const TABLE_POSTFIX = 'Table'; export const NAME_POSTFIX = 'Name'; export const ID_POSTFIX = 'Id'; export const PRIORITY_POSTFIX = 'Priority'; export function setVariable(variableKey: string, variableValue: any) { const componentPath = getProjectPath(); const variablesFilePath = path.join(componentPath, 'variables.yaml'); let componentVariables = parse(fs.readFileSync(variablesFilePath, 'utf-8')); componentVariables['variables'][variableKey] = variableValue; fs.writeFileSync( variablesFilePath, stringify(componentVariables, { keepSourceTokens: true, lineWidth: 0 }), ); } export function transformVariables(content: string) { const componentPath = getProjectPath(); const variablesFilePath = path.join(componentPath, 'variables.yaml'); let componentVariables = parse(fs.readFileSync(variablesFilePath, 'utf-8'))[ 'variables' ] as any; const keys = Object.keys(componentVariables); for (var i = 0; i < keys.length; i++) { const key = keys[i]; const value = componentVariables[key]; content = content.replace(key, value); } return content; } export function searchComponents( pattern: string, componentPath: string = '', ): Array { const projectComponentPath = getProjectPath(); componentPath = componentPath || projectComponentPath; // search files in the project let paths = globSync(path.resolve(path.join(componentPath, `**/*${pattern}.yaml`)), { windowsPathsNoEscape: true, }) || []; // search files includes in main.yaml (may be an external file) const mainYml = path.resolve(projectComponentPath) === path.resolve(componentPath) ? parse( fs.readFileSync( path.join(projectComponentPath, 'main.yaml'), 'utf-8', ), ) || {} : {}; const endsWithName = `${pattern}.yaml`; const externals = (mainYml.externals || []) .filter( (e: { type: string; path: string }) => e.type === 'local' && e.path.length >= endsWithName.length && e.path.substring(e.path.length - endsWithName.length) === endsWithName, ) .map((e: { type: string; path: string }) => path.resolve(path.join(projectComponentPath, e.path)), ); // distinct the file paths paths = [...paths, ...externals].filter((v, i, a) => a.indexOf(v) === i); var files = paths.map((f) => { var result = parse(fs.readFileSync(f, 'utf-8')); result.__file_path = f; return result; }); return files; } export function includeYaml( type: string, filePath: string, replacements: IExternalReplacement[] = [], ) { const componentPath = getProjectPath(); const mainYamlPath = path.join(componentPath, 'main.yaml'); const mainYaml = parse(fs.readFileSync(mainYamlPath, 'utf-8')); const relativePath = path.join( path.relative(path.dirname(mainYamlPath), path.dirname(filePath)), filePath.replace(/^.*[\\\/]/, ''), ); const externals = !!replacements?.length ? mainYaml.externals || [] : mainYaml.externals?.filter( (e: any) => e.type !== type || e.path !== relativePath || !!e.replacements?.length, ) || []; externals.push({ type, path: relativePath, replacements: [...replacements], }); mainYaml.externals = [...externals]; fs.writeFileSync( mainYamlPath, stringify(mainYaml, { keepSourceTokens: true, lineWidth: 0 }), ); } export interface INormalizedName { displayName: string; underscoreDisplayName: string; camelCaseName: string; dashedSlug: string; underscoreSlug: string; underscoreUpperCase: string; } export function normalizeName(name: string): INormalizedName { // To DisplayName const displayName = name ?.toLowerCase() .replace(/^([a-zA-Z])/g, (w) => w.toUpperCase()) .replace(/[^a-zA-Z0-9]+([a-zA-Z])/g, (w) => w.toUpperCase()) .replace(/[^a-zA-Z0-9\s]+/g, ' '); // Updated underscoreDisplayName logic const underscoreDisplayName = name .replace(/[^a-zA-Z0-9]+/g, '_') // Replace all special characters with "_" .replace(/(?:^|_)([a-zA-Z])/g, (match, p1) => p1.toUpperCase()) // Capitalize each word .replace(/^_+|_+$/g, ''); // Remove leading and trailing underscores const underscoreUpperCase = name .replace(/[^a-zA-Z0-9]+/g, '_') // Replace all special characters with "_" .replace(/^_+|_+$/g, '') // Remove leading and trailing underscores .toUpperCase(); // CamelCase const camelCaseName = displayName.replace(/[^a-zA-Z0-9]+/g, ''); const dashedSlug = displayName.replace(/[^a-zA-Z0-9]+/g, '-').toLowerCase(); const underscoreSlug = displayName .replace(/[^a-zA-Z0-9]+/g, '_') .toLowerCase(); return { displayName, underscoreDisplayName, camelCaseName, dashedSlug, underscoreSlug, underscoreUpperCase, }; } export function listComponents( pattern: string, getConfig: Function, componentPath: string = '', ): Array { let files = (searchComponents(pattern, componentPath) as Array) || []; let data: Array = []; for (var i = 0; i < files.length; i++) { const file = files[i] as any; const configs = getConfig(file) as Array; if (!configs || !configs.length) { continue; } for (var j = 0; j < configs.length; j++) { let config = configs[j] as any; config.__real_name = readVariableValue(config.name); config.__file_path = file.__file_path; data.push(config); } } return data; } export async function searchComponentByName( name: string, searchPath: string, type: string, searchKeys: Array<{ name: string; weight: number }>, getConfig: Function, componentPath: string = '', ) { let files = (searchComponents(searchPath, componentPath) as Array) || []; let data: Array = []; for (let i = 0; i < files.length; i++) { const file = files[i] as any; const configs = getConfig(file) as Array; if (!configs || !configs.length) { continue; } for (let j = 0; j < configs.length; j++) { let config = configs[j] as any; config.__real_name = readVariableValue(config.name, componentPath); config.__file_path = file.__file_path; data.push(config); } } const fuse = new Fuse(data, { includeScore: true, keys: [ '__real_name', // will be assigned a `weight` of 1 ...searchKeys, ], }); const searchResult = fuse .search(name) .sort((a: any, b: any) => parseFloat(a.score) - parseFloat(b.score)); if (searchResult.length === 0) { console.log(`There is no such ${type} "${name}"`); // TODO: more intelligent by showing off "Did you mean xxx, yyy, zzz" return null; } if (searchResult.length === 1) { // Automatically select the single result return searchResult[0].item; } // If multiple results, prompt the user to select one const answer = await inquirer.prompt<{ selected: string }>([ { type: 'list', name: 'selected', message: `Please select one of the ${type}(s) below:`, choices: searchResult.map( (x: any, i) => `${x.item.__real_name ? x.item.__real_name : x.item.id}`, ), }, ]); const selectedItem = searchResult.find( (x: any) => `${x.item.__real_name ? x.item.__real_name : x.item.id}` === answer.selected, ); return selectedItem?.item || null; } export async function searchComponentsByName( name: string, searchPath: string, type: string, searchKeys: Array<{ name: string; weight: number }>, getConfig: (file: any) => Array, componentPath: string = '', ): Promise { let files = (searchComponents(searchPath, componentPath) as Array) || []; let data: Array = []; for (let i = 0; i < files.length; i++) { const file = files[i] as any; const configs = getConfig(file) as Array; if (!configs || !configs.length) { continue; } for (let j = 0; j < configs.length; j++) { let config = configs[j] as any; config.__real_name = readVariableValue(config.name, componentPath); config.__file_path = file.__file_path; data.push(config); } } const fuse = new Fuse(data, { includeScore: true, keys: ['__real_name', ...searchKeys], }); const searchResult = fuse .search(name) .sort((a: any, b: any) => parseFloat(a.score) - parseFloat(b.score)); if (searchResult.length === 0) { console.log(`There is no such ${type} "${name}"`); return []; } if (searchResult.length === 1) { return [searchResult[0].item]; } const choices = searchResult.map((x: any, i: number) => { const display = `${x.item.__real_name ? x.item.__real_name : x.item.id}`; const filePath = x.item.__file_path || ''; return { name: `${i + 1}. ${display}${filePath ? `\n File Path: ${filePath}` : ''}`, value: i, }; }); const answer = await inquirer.prompt<{ selected: number[] }>([ { type: 'checkbox', name: 'selected', message: `Please select one or more of the ${type}(s) below:`, choices, validate: (val: number[]) => val && val.length ? true : 'Please select at least one item', }, ]); const indexes = (answer.selected || []).filter( (idx) => idx >= 0 && idx < searchResult.length, ); return indexes.map((idx) => searchResult[idx].item); } export async function searchTasks(name: string, componentPath: string = '') { return searchComponentsByName( name, 'Task', 'task', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.objects || [], componentPath, ); } export async function searchReports(name: string, componentPath: string = '') { return searchComponentsByName( name, 'Report', 'report', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.reports || [], componentPath, ); } export async function searchSchemas(name: string, componentPath: string = '') { return searchComponentsByName( name, 'Schema', 'schema', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.schemas || [], componentPath, ); } export async function searchConnections( name: string, componentPath: string = '', ): Promise { return await searchComponentsByName( name, 'Connection', 'connection', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.connectors || [], componentPath, ); } export async function searchPushers(name: string, componentPath: string = '') { return searchComponentsByName( name, 'Pusher', 'pusher', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.pushers || [], componentPath, ); } export async function searchDependencies( name: string, componentPath: string = '', ) { return searchComponentsByName( name, 'Dependency', 'dependency', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, { name: 'dependencyKey', weight: 4, }, ], (f: any) => { const deps = f?.dependencies; if (!deps) return []; if (Array.isArray(deps)) return deps; // Support map format: { [key]: Dependency | Dependency[] } if (typeof deps === 'object') { return Object.values(deps).flat().filter(Boolean); } return []; }, componentPath, ); } export async function searchTransformations( name: string, componentPath: string = '', ) { return searchComponentsByName( name, 'Transformation', 'transformation', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, { name: 'type', weight: 4, }, ], (f: any) => { const t = f?.transformations; if (!t) return []; if (Array.isArray(t)) return t; // Common format: { [taskId]: Transformation[] } if (typeof t === 'object') { return Object.values(t).flat().filter(Boolean); } return []; }, componentPath, ); } export async function searchValidations( name: string, componentPath: string = '', ) { return searchComponentsByName( name, 'Validation', 'validation', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, { name: 'type', weight: 4, }, ], (f: any) => { const v = f?.validations; if (!v) return []; if (Array.isArray(v)) return v; // Common format: { [taskId]: Validation[] } if (typeof v === 'object') { return Object.values(v).flat().filter(Boolean); } return []; }, componentPath, ); } export async function searchTask(name: string, componentPath: string = '') { return searchComponentByName( name, 'Task', 'task', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.objects || [], componentPath, ); } export async function searchReport(name: string, componentPath: string = '') { return searchComponentByName( name, 'Report', 'report', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.reports || [], componentPath, ); } export async function searchSchema(name: string, componentPath: string = '') { return searchComponentByName( name, 'Schema', 'schema', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.schemas || [], componentPath, ); } export async function searchWebhook(name: string) { return searchComponentByName( name, 'Webhook', 'webhook', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.webhooks || [], ); } export async function searchPusher(name: string) { return searchComponentByName( name, 'Pusher', 'pusher', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.pushers || [], ); } export async function searchDependency( name: string, componentPath: string = '', ) { return searchComponentByName( name, 'Dependency', 'dependency', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, { name: 'dependencyKey', weight: 4, }, ], (f: any) => { const deps = f?.dependencies; if (!deps) return []; if (Array.isArray(deps)) return deps; if (typeof deps === 'object') { return Object.values(deps).flat().filter(Boolean); } return []; }, componentPath, ); } export async function searchTransformation( name: string, componentPath: string = '', ) { return searchComponentByName( name, 'Transformation', 'transformation', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, { name: 'type', weight: 4, }, ], (f: any) => { const t = f?.transformations; if (!t) return []; if (Array.isArray(t)) return t; if (typeof t === 'object') { return Object.values(t).flat().filter(Boolean); } return []; }, componentPath, ); } export async function searchValidation( name: string, componentPath: string = '', ) { return searchComponentByName( name, 'Validation', 'validation', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, { name: 'type', weight: 4, }, ], (f: any) => { const v = f?.validations; if (!v) return []; if (Array.isArray(v)) return v; if (typeof v === 'object') { return Object.values(v).flat().filter(Boolean); } return []; }, componentPath, ); } export async function searchConnection( name: string, componentPath: string = '', ): Promise { return await searchComponentByName( name, 'Connection', 'connection', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.connectors || [], componentPath, ); } export async function searchPuller(name: string) { return searchComponentByName( name, 'Puller', 'puller', [ { name: 'name', weight: 2, }, { name: 'description', weight: 3, }, ], (f: any) => f?.pullers || [], ); } export async function searchTable(name: string) { return searchComponentByName( name, 'Table', 'table', [ { name: 'name', weight: 2, }, { name: 'id', weight: 3, }, ], (f: any) => f?.tables || [], ); } export function searchComponentByIdKey( idKey: string, searchPath: string, getConfigs: Function, componentPath: string = '', ): any { let files = (searchComponents(searchPath, componentPath) as Array) || []; if (!files || !files.length) { return null; } for (var i = 0; i < files.length; i++) { const file = files[i]; const configs = getConfigs(file); if (!configs || !configs.length) { continue; } for (var j = 0; j < configs.length; j++) { const config = configs[j]; if (config.id.toLowerCase() === idKey.toLowerCase()) { config.__file_path = file.__file_path; config.__real_name = readVariableValue(config.name || ''); return config; } } } return null; } export function findTable(idKey: string): any { return searchComponentByIdKey(idKey, 'Table', (f: any) => f?.tables || []); } export function findPuller(idKey: string): any { return searchComponentByIdKey(idKey, 'Puller', (f: any) => f?.pullers || []); } export function findConnection(idKey: string): IConnection | null { return searchComponentByIdKey( idKey, 'Connection', (f: any) => f?.connectors || [], ); } export function extractPullerResponseFilters(puller: { responseFilters: any; }): string[] { return Object.keys(puller?.responseFilters || {}).filter( (x) => x != 'hasNextPage' && x != '__is_direct_index' && x != '__is_hybrid_index' && x != '__is_full_pull', ); } export function extractSqlStepResponseFields( responseFilter: string, responseFilters: { sql: string; resultSets?: Dictionary; responseFilters?: { key: string; type: string; fields: string[] }[]; }, ): string[] { // TODO: this is too complex, people might select * and we need to connect to the database for responses if (responseFilters?.responseFilters?.length) { const filter = responseFilters.responseFilters.find( (x) => x.key === responseFilter, ); if (filter) { return filter.fields || []; } } return []; } export function extractJsonataFields(expression: string): string[] { const reg = /"(.+)":/g; let matches = (expression?.match(reg) || []).map((e: string) => e.replace(reg, '$1'), ); return matches; } export function extractApiStepResponseFields( responseFilter: string, data: Dictionary>, ): string[] { const keys = Object.keys(data); for (var i = 0; i < keys.length; i++) { const key = keys[i]; if (!data[key] || !data[key][responseFilter]) { continue; } switch (key) { case 'jsonata': return extractJsonataFields(data[key][responseFilter].expression); case 'jsonpath': // TODO: implement this; throw new Error(`Response reader for ${keys[i]} is not supported`); case 'xpath': // TODO: implement this; throw new Error(`Response reader for ${keys[i]} is not supported`); default: throw new Error(`Response reader for ${keys[i]} is not supported`); } } return []; } export function extractExpressionByParser( parserType: string, parserExpression: any, ) { switch (parserType) { case 'jsonata': case 'jsonpath': case 'xpath': return parserExpression?.expression; default: throw new Error( `Cannot extract expression for parser type ${parserType}`, ); } } export function extractExpression(resultKey: string, pullDataStep: any) { switch (pullDataStep.displayType) { case 'API': const responseFilters = pullDataStep.data?.responseFilters || {}; const parserType = Object.keys(responseFilters); for (var i = 0; i < parserType.length; i++) { if (!!responseFilters[parserType[i]][resultKey]) { return extractExpressionByParser( parserType[i], responseFilters[parserType[i]][resultKey], ); } } // return extractApiStepResponseFields(responseFilter, data.responseFilters); default: throw new Error( `Cannot extract expression for step type ${pullDataStep.displayType}`, ); } } export function extractStepResponseFields( responseFilter: string, type: string, data: any, ): string[] { switch (type) { case 'SQL': return extractSqlStepResponseFields(responseFilter, data); case 'API': return extractApiStepResponseFields(responseFilter, data.responseFilters); default: throw new Error(`The step type ${type} is not supported`); } } export function getValidations(componentPath: string = ''): any { componentPath = componentPath || getProjectPath(); const filePath = path.join(componentPath, 'ObjectValidations.yaml'); if (!fs.existsSync(filePath)) { fs.writeFileSync( filePath, stringify({ validations: {} }, { keepSourceTokens: true, lineWidth: 0 }), ); } var config = parse(fs.readFileSync(filePath, 'utf8')) as any; return { ...config, __file_path: filePath, }; } export function getTransformations(componentPath: string = ''): any { componentPath = componentPath || getProjectPath(); const filePath = path.join(componentPath, 'ObjectTransformations.yaml'); if (!fs.existsSync(filePath)) { fs.writeFileSync( filePath, stringify( { transformations: {} }, { keepSourceTokens: true, lineWidth: 0 }, ), ); } var config = parse(fs.readFileSync(filePath, 'utf8')) as any; return { ...config, __file_path: filePath, }; } export function getAssociation(componentPath: string = ''): any { componentPath = componentPath || getProjectPath(); const objectAssociationPath = path.join( componentPath, 'ObjectAssociations.yaml', ); if (!fs.existsSync(objectAssociationPath)) { fs.writeFileSync(objectAssociationPath, objectAssociationYaml()); } var objectAssociations = parse( fs.readFileSync(objectAssociationPath, 'utf8'), ) as any; return { ...objectAssociations, __file_path: objectAssociationPath, }; } export function getTasks(componentPath: string = ''): any[] { componentPath = componentPath || getProjectPath(); const tasksPath = path.join(componentPath, 'objects'); if (!fs.existsSync(tasksPath)) { throw new Error(`The directory ${tasksPath} does not exist.`); } const taskFiles = fs .readdirSync(tasksPath) .filter((file) => file.endsWith('_Task.yaml')); return taskFiles.map((file) => { const filePath = path.join(tasksPath, file); const taskConfig = parse(fs.readFileSync(filePath, 'utf8')); return { taskConfig, // Task configuration __file_path: filePath, // Include the file path for reference }; }); } export function getColumnsFromTasks( tasks: Array<{ taskConfig: any; __file_path: string }>, ): string[] { const allColumns = new Set(); tasks.forEach(({ __file_path }) => { // Replace 'Task' with 'Table' in the filename const tableFileName = path.basename(__file_path).replace('Task', 'Table'); const tableFilePath = path.join(path.dirname(__file_path), tableFileName); if (fs.existsSync(tableFilePath)) { // Read and parse the table file const tableConfig = parse(fs.readFileSync(tableFilePath, 'utf8')); // Extract columns from the table configuration structure const columns = Object.keys(tableConfig.tables[0]?.columns || {}); columns.forEach((col) => allColumns.add(col)); } else { console.warn(`Table file not found for task file: ${__file_path}`); } }); // Convert the Set to an Array, sort it alphabetically, and return return Array.from(allColumns).sort((a, b) => a.localeCompare(b)); } export function generateAssociation( taskIdKey: string, table?: { id: string } | null, puller?: { id: string; resultKey: string } | null, pusher?: { id: string } | null, ): { filePath: string; config: any; } { const objectAssociations = getAssociation(); if (!objectAssociations.__file_path) { throw new Error(`The objectAssociation.__file_path is not provided. Please contact HexaSync Administrators for bugs fixing. Got ${objectAssociations.__file_path}`); } let yml = objectAssociations['objectAssociations'][taskIdKey] || {}; yml.puller = { id: puller?.id || yml.puller?.id, resultKey: puller?.resultKey || yml.puller?.resultKey, }; yml.table = { id: table?.id || yml.table?.id }; yml.pusher = { id: pusher?.id || yml.pusher?.id }; objectAssociations['objectAssociations'][taskIdKey] = { ...yml, }; const filePath = objectAssociations.__file_path; delete objectAssociations.__file_path; return { filePath: filePath, config: objectAssociations, }; } export function generateTask( normalizedConnectionName: INormalizedName, normalizedName: INormalizedName, ): { idKey: string; filePath: string; config: any; variables: Array<{ key: string; value: any }>; } { var variables = [ { field: 'id', key: `**${normalizedConnectionName.camelCaseName}_${normalizedName.camelCaseName}_${OBJECT_POSTFIX}_Id**`, value: v4(), }, { field: 'priority', key: `**${normalizedConnectionName.camelCaseName}_${normalizedName.camelCaseName}_${OBJECT_POSTFIX}_Priority**`, value: 999, }, { field: 'name', key: `**${normalizedConnectionName.camelCaseName}_${normalizedName.camelCaseName}_${OBJECT_POSTFIX}_Name**`, value: `[${normalizedConnectionName.displayName}] ${normalizedName.displayName}`, }, { field: 'valueTableName', key: `**${normalizedConnectionName.camelCaseName}_${normalizedName.camelCaseName}_Table_Name**`, value: `__hss_${normalizedConnectionName.underscoreSlug}_${normalizedName.underscoreSlug}`, }, ]; let yml = objectYaml(); for (var i = 0; i < variables.length; i++) { var variable = variables[i]; yml = yml.replaceAll(`||VARIABLE_KEY_${variable.field}||`, variable.key); } const componentPath = getProjectPath(); return { idKey: `**${normalizedConnectionName.camelCaseName}_${normalizedName.camelCaseName}_${OBJECT_POSTFIX}_Id**`, filePath: path.join( componentPath, 'objects', `${normalizedConnectionName.camelCaseName}_${normalizedName.camelCaseName}_${OBJECT_POSTFIX}.yaml`, ), variables, config: parse(yml), }; } export function generateTable( normalizedConnectionName: INormalizedName, normalizedName: INormalizedName, fields: string[], ): { idKey: string; filePath: string; config: any; variables: Array<{ key: string; value: any }>; } { var variables = [ { field: 'id', key: `**${normalizedConnectionName.camelCaseName}_${normalizedName.camelCaseName}_Table_Id**`, value: v4(), }, ]; let yml = tableYaml(); for (var i = 0; i < variables.length; i++) { var variable = variables[i]; yml = yml.replaceAll(`||VARIABLE_KEY_${variable.field}||`, variable.key); } const config = parse(yml); for (var i = 0; i < fields.length; i++) { // todo: this should be smarter config['tables'][0].columns[fields[i]] = { indexGroups: [] }; } const componentPath = getProjectPath(); return { idKey: `**${normalizedConnectionName.camelCaseName}_${normalizedName.camelCaseName}_Table_Id**`, filePath: path.join( componentPath, 'objects', `${normalizedConnectionName.camelCaseName}_${normalizedName.camelCaseName}_Table.yaml`, ), variables, config: config, }; } export function getTaskColumn(taskObj) { var template = getProjectOutput(); var tables = template.tables || []; var tableId = (template.objectAssociations || {})[taskObj.id]?.table?.id; var tableColumns = (!!tableId && tables.find((table: any) => table.id === tableId)?.columns) || {}; let columns: { name: string; visibility: string | number }[] = Object.keys( tableColumns, ).map((key) => ({ name: key, visibility: tableColumns[key].visibility })); if (columns.length === 0) { tableColumns = taskObj.puller?.mappings || {}; columns = Object.keys(tableColumns).map((key) => ({ name: key, visibility: tableColumns[key].visibility, })); } return columns; } export function getTask(taskIdVariable) { // console.log(chalk.green(`Searching for task with id: ${taskIdVariable}`)); const projectPath = getProjectPath(); // 1. List out all the *.yaml files recursively in projectPath const yamlFiles = globSync(`${projectPath}/**/*.yaml`, { windowsPathsNoEscape: true, }); let taskObj: any = null; for (const filePath of yamlFiles) { const fileContent = fs.readFileSync(filePath, 'utf8'); // 2. For each file, parse the file using yaml parse() function to `ymlConfig` object name const ymlConfig = parse(fileContent); // 2.1 If the ymlConfig does not have key `objects`, or its value is not an array, or array length is 0, continue; if ( !ymlConfig.objects || !Array.isArray(ymlConfig.objects) || ymlConfig.objects.length === 0 ) { continue; } // 3. For each object in ymlConfig.objects, check if the object has key `id` and its value is equal to `taskId` for (const obj of ymlConfig.objects) { if (obj.id === taskIdVariable) { taskObj = { ...obj }; break; } } } return taskObj; }