yamchart 0.3.1 → 0.3.3

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.
Files changed (33) hide show
  1. package/dist/chunk-4P5UHWYK.js +247 -0
  2. package/dist/chunk-4P5UHWYK.js.map +1 -0
  3. package/dist/chunk-A24KVXJQ.js +922 -0
  4. package/dist/chunk-A24KVXJQ.js.map +1 -0
  5. package/dist/chunk-DGUM43GV.js +11 -0
  6. package/dist/chunk-DGUM43GV.js.map +1 -0
  7. package/dist/chunk-LL3VKMZM.js +1265 -0
  8. package/dist/chunk-LL3VKMZM.js.map +1 -0
  9. package/dist/dev-PTVNJI7G.js +13557 -0
  10. package/dist/dev-PTVNJI7G.js.map +1 -0
  11. package/dist/dist-JH7OL7U4.js +16 -0
  12. package/dist/dist-JH7OL7U4.js.map +1 -0
  13. package/dist/dist-JKJLH3F6.js +56 -0
  14. package/dist/dist-JKJLH3F6.js.map +1 -0
  15. package/dist/{generate-RD3LCS73.js → generate-KNER36CB.js} +3 -1
  16. package/dist/{generate-RD3LCS73.js.map → generate-KNER36CB.js.map} +1 -1
  17. package/dist/index.js +13 -11
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-6D5VNGSP.js → init-FTSEOTAD.js} +3 -1
  20. package/dist/{init-6D5VNGSP.js.map → init-FTSEOTAD.js.map} +1 -1
  21. package/dist/{reset-password-MJ54ICGP.js → reset-password-IZQTDTU7.js} +3 -2
  22. package/dist/{reset-password-MJ54ICGP.js.map → reset-password-IZQTDTU7.js.map} +1 -1
  23. package/dist/{sync-dbt-IDDD4X2Z.js → sync-dbt-6WY7HKP7.js} +3 -1
  24. package/dist/{sync-dbt-IDDD4X2Z.js.map → sync-dbt-6WY7HKP7.js.map} +1 -1
  25. package/dist/{test-N4KIIKQN.js → test-WYNX4RYZ.js} +11 -10
  26. package/dist/{test-N4KIIKQN.js.map → test-WYNX4RYZ.js.map} +1 -1
  27. package/dist/{update-QHLCWS56.js → update-GWPF5AS6.js} +2 -1
  28. package/dist/{update-QHLCWS56.js.map → update-GWPF5AS6.js.map} +1 -1
  29. package/package.json +25 -7
  30. package/dist/chunk-6GDL3DH4.js +0 -354
  31. package/dist/chunk-6GDL3DH4.js.map +0 -1
  32. package/dist/dev-HMLMSTA7.js +0 -106
  33. package/dist/dev-HMLMSTA7.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/sync-dbt.ts","../src/dbt/local-source.ts","../src/dbt/parser.ts","../src/dbt/scanner.ts","../src/dbt/catalog.ts"],"sourcesContent":["import { mkdir, writeFile, readFile, access } from 'fs/promises';\nimport { join } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { LocalDbtSource } from '../dbt/local-source.js';\nimport { scanYamchartModels } from '../dbt/scanner.js';\nimport { generateCatalogMd, generateCatalogJson, type CatalogData, type CatalogModel } from '../dbt/catalog.js';\nimport type { DbtSourceConfig } from '../dbt/types.js';\n\nexport interface SyncDbtOptions {\n source: 'local' | 'github' | 'dbt-cloud';\n path?: string;\n repo?: string;\n branch?: string;\n include: string[];\n exclude: string[];\n tags: string[];\n refresh?: boolean;\n}\n\nexport interface SyncDbtResult {\n success: boolean;\n modelsIncluded: number;\n modelsExcluded: number;\n catalogPath: string;\n error?: string;\n}\n\n/**\n * Load saved sync config from .yamchart/dbt-source.yaml\n */\nexport async function loadSyncConfig(projectDir: string): Promise<DbtSourceConfig | null> {\n const configPath = join(projectDir, '.yamchart', 'dbt-source.yaml');\n\n try {\n await access(configPath);\n const content = await readFile(configPath, 'utf-8');\n return parseYaml(content) as DbtSourceConfig;\n } catch {\n return null;\n }\n}\n\n/**\n * Sync dbt project metadata to yamchart catalog.\n *\n * This function:\n * 1. Ensures .yamchart directory exists\n * 2. Handles refresh mode (load saved config)\n * 3. Validates source type (only 'local' supported for v1)\n * 4. Creates LocalDbtSource, lists models, applies filters\n * 5. Uses smart defaults if no filters specified\n * 6. Scans yamchart models for cross-references\n * 7. Generates catalog.md and catalog.json\n * 8. Saves sync config to dbt-source.yaml\n */\nexport async function syncDbt(\n projectDir: string,\n options: SyncDbtOptions\n): Promise<SyncDbtResult> {\n const yamchartDir = join(projectDir, '.yamchart');\n const catalogPath = join(yamchartDir, 'catalog.md');\n\n try {\n // Step 1: Ensure .yamchart directory exists\n await mkdir(yamchartDir, { recursive: true });\n\n // Step 2: Handle refresh mode - load saved config\n let effectiveOptions = { ...options };\n if (options.refresh) {\n const savedConfig = await loadSyncConfig(projectDir);\n if (!savedConfig) {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: 'No saved sync config found. Run sync-dbt with --path first.',\n };\n }\n\n effectiveOptions = {\n source: savedConfig.source,\n path: savedConfig.path,\n repo: savedConfig.repo,\n branch: savedConfig.branch,\n include: savedConfig.filters.include,\n exclude: savedConfig.filters.exclude,\n tags: savedConfig.filters.tags,\n };\n }\n\n // Step 3: Validate source type (only 'local' supported for v1)\n if (effectiveOptions.source !== 'local') {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: `Source type \"${effectiveOptions.source}\" not yet supported. Only \"local\" is available.`,\n };\n }\n\n if (!effectiveOptions.path) {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: 'Path to dbt project is required for local source.',\n };\n }\n\n // Step 4: Create LocalDbtSource, list models\n const dbtSource = new LocalDbtSource(effectiveOptions.path);\n const allModels = await dbtSource.listModels();\n\n // Step 5: Apply filters or use smart defaults\n let hasFilters =\n effectiveOptions.include.length > 0 ||\n effectiveOptions.exclude.length > 0 ||\n effectiveOptions.tags.length > 0;\n\n let filteredModels;\n if (hasFilters) {\n filteredModels = LocalDbtSource.filterModels(allModels, {\n include: effectiveOptions.include,\n exclude: effectiveOptions.exclude,\n tags: effectiveOptions.tags,\n });\n } else {\n // Use smart defaults - prefer marts/reporting, exclude staging/intermediate\n const defaults = LocalDbtSource.getDefaultFilters();\n const withDefaults = LocalDbtSource.filterModels(allModels, defaults);\n\n // If defaults filter out everything, include all models\n filteredModels = withDefaults.length > 0 ? withDefaults : allModels;\n }\n\n const modelsExcluded = allModels.length - filteredModels.length;\n\n // Get full model details for included models\n const modelNames = filteredModels.map(m => m.name);\n const fullModels = await dbtSource.getModels(modelNames);\n\n // Step 6: Scan yamchart models for cross-references\n const yamchartModels = await scanYamchartModels(projectDir);\n\n // Build catalog models with cross-references\n const catalogModels: CatalogModel[] = fullModels.map(model => {\n // Find yamchart models that reference this dbt model\n const referencingModels = yamchartModels.filter(ym =>\n ym.source === model.name || ym.source === model.table\n );\n\n return {\n ...model,\n yamchartModels: referencingModels,\n };\n });\n\n // Step 7: Generate catalog files\n const catalogData: CatalogData = {\n syncedAt: new Date().toISOString(),\n source: {\n type: effectiveOptions.source,\n path: effectiveOptions.path,\n repo: effectiveOptions.repo,\n },\n stats: {\n modelsIncluded: filteredModels.length,\n modelsExcluded,\n },\n models: catalogModels,\n };\n\n const catalogMd = generateCatalogMd(catalogData);\n const catalogJson = generateCatalogJson(catalogData);\n\n await writeFile(catalogPath, catalogMd, 'utf-8');\n await writeFile(join(yamchartDir, 'catalog.json'), catalogJson, 'utf-8');\n\n // Step 8: Save sync config for re-sync\n const syncConfig: DbtSourceConfig = {\n source: effectiveOptions.source,\n path: effectiveOptions.path,\n repo: effectiveOptions.repo,\n branch: effectiveOptions.branch,\n lastSync: catalogData.syncedAt,\n filters: {\n include: effectiveOptions.include,\n exclude: effectiveOptions.exclude,\n tags: effectiveOptions.tags,\n },\n stats: {\n modelsIncluded: filteredModels.length,\n modelsExcluded,\n },\n };\n\n const configYaml = stringifyYaml(syncConfig);\n await writeFile(join(yamchartDir, 'dbt-source.yaml'), configYaml, 'utf-8');\n\n return {\n success: true,\n modelsIncluded: filteredModels.length,\n modelsExcluded,\n catalogPath,\n };\n } catch (err) {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: err instanceof Error ? err.message : 'Unknown error during sync',\n };\n }\n}\n","import { readFile } from 'fs/promises';\nimport { join } from 'path';\nimport fg from 'fast-glob';\nimport { minimatch } from 'minimatch';\nimport { parseSchemaYml, parseProjectYml } from './parser.js';\nimport type {\n DbtSource,\n DbtProjectConfig,\n DbtModel,\n DbtModelSummary,\n} from './types.js';\n\nexport interface ModelFilters {\n include?: string[];\n exclude?: string[];\n tags?: string[];\n}\n\n/**\n * LocalDbtSource reads dbt project metadata from a local filesystem.\n * It parses schema.yml files to extract model definitions, columns, and hints.\n */\nexport class LocalDbtSource implements DbtSource {\n readonly type = 'local' as const;\n private projectPath: string;\n private modelsCache: Map<string, DbtModel> | null = null;\n private configCache: DbtProjectConfig | null = null;\n\n constructor(projectPath: string) {\n this.projectPath = projectPath;\n }\n\n /**\n * Read and parse dbt_project.yml\n */\n async getProjectConfig(): Promise<DbtProjectConfig> {\n if (this.configCache) {\n return this.configCache;\n }\n\n const configPath = join(this.projectPath, 'dbt_project.yml');\n const content = await readFile(configPath, 'utf-8');\n const parsed = parseProjectYml(content);\n\n this.configCache = {\n name: parsed.name,\n version: parsed.version,\n profile: parsed.profile,\n model_paths: parsed.modelPaths,\n target_path: 'target',\n vars: parsed.vars,\n };\n\n return this.configCache;\n }\n\n /**\n * Find all schema.yml files and parse models from them.\n * Returns summaries for model selection UI.\n */\n async listModels(): Promise<DbtModelSummary[]> {\n await this.loadModels();\n\n const summaries: DbtModelSummary[] = [];\n for (const model of this.modelsCache!.values()) {\n summaries.push({\n name: model.name,\n path: model.path,\n description: model.description || 'No description',\n tags: model.tags || [],\n });\n }\n\n return summaries;\n }\n\n /**\n * Get full model details by name.\n * @throws Error if model not found\n */\n async getModel(name: string): Promise<DbtModel> {\n await this.loadModels();\n\n const model = this.modelsCache!.get(name);\n if (!model) {\n throw new Error(`Model not found: ${name}`);\n }\n\n return model;\n }\n\n /**\n * Get multiple models by name.\n * @throws Error if any model not found\n */\n async getModels(names: string[]): Promise<DbtModel[]> {\n if (names.length === 0) {\n return [];\n }\n\n const models: DbtModel[] = [];\n for (const name of names) {\n models.push(await this.getModel(name));\n }\n\n return models;\n }\n\n /**\n * Load all models from schema.yml files into cache.\n */\n private async loadModels(): Promise<void> {\n if (this.modelsCache) {\n return;\n }\n\n const config = await this.getProjectConfig();\n this.modelsCache = new Map();\n\n // Find all schema.yml files in model paths\n for (const modelPath of config.model_paths) {\n const pattern = join(this.projectPath, modelPath, '**/*.yml');\n const files = await fg(pattern, {\n ignore: ['**/node_modules/**'],\n });\n\n for (const file of files) {\n const content = await readFile(file, 'utf-8');\n // Get relative path from project root\n const relativePath = file.slice(this.projectPath.length + 1);\n const models = parseSchemaYml(content, relativePath);\n\n for (const model of models) {\n this.modelsCache.set(model.name, model);\n }\n }\n }\n }\n\n /**\n * Filter models by include/exclude glob patterns and tags.\n * Patterns match against model paths.\n */\n static filterModels(\n models: DbtModelSummary[],\n filters: ModelFilters\n ): DbtModelSummary[] {\n let filtered = [...models];\n\n // Apply include patterns (match any)\n if (filters.include && filters.include.length > 0) {\n filtered = filtered.filter((model) =>\n filters.include!.some((pattern) => minimatch(model.path, pattern))\n );\n }\n\n // Apply exclude patterns (exclude any matches)\n if (filters.exclude && filters.exclude.length > 0) {\n filtered = filtered.filter(\n (model) =>\n !filters.exclude!.some((pattern) => minimatch(model.path, pattern))\n );\n }\n\n // Apply tag filters (match any)\n if (filters.tags && filters.tags.length > 0) {\n filtered = filtered.filter((model) =>\n filters.tags!.some((tag) => model.tags.includes(tag))\n );\n }\n\n return filtered;\n }\n\n /**\n * Get default filters that focus on marts/reporting models.\n * These are typically the models most useful for BI dashboards.\n */\n static getDefaultFilters(): Required<ModelFilters> {\n return {\n include: ['**/marts/**', '**/reporting/**'],\n exclude: ['**/staging/**', '**/intermediate/**'],\n tags: [],\n };\n }\n}\n","import { parse as parseYaml } from 'yaml';\nimport { dirname, join } from 'path';\nimport type { DbtModel, DbtColumn } from './types.js';\n\ninterface RawSchemaYml {\n version: number;\n models?: RawModel[];\n}\n\ninterface RawModel {\n name: string;\n description?: string;\n meta?: Record<string, unknown>;\n tags?: string[];\n columns?: RawColumn[];\n}\n\ninterface RawColumn {\n name: string;\n description?: string;\n data_type?: string;\n tests?: (string | Record<string, unknown>)[];\n}\n\n/**\n * Extract hints from dbt column tests.\n * - unique → \"unique\"\n * - not_null → \"required\"\n * - relationships: { to: ref('X') } → \"fk:X\"\n */\nexport function extractHintsFromTests(tests: (string | Record<string, unknown>)[]): string[] {\n const hints: string[] = [];\n\n for (const test of tests) {\n if (typeof test === 'string') {\n if (test === 'unique') {\n hints.push('unique');\n } else if (test === 'not_null') {\n hints.push('required');\n } else if (test === 'primary_key') {\n hints.push('primary_key');\n }\n } else if (typeof test === 'object' && test !== null) {\n // Handle relationships test\n if ('relationships' in test) {\n const rel = test.relationships as { to?: string; field?: string };\n if (rel.to) {\n // Extract table name from ref('table_name')\n const match = rel.to.match(/ref\\(['\"]([^'\"]+)['\"]\\)/);\n if (match) {\n hints.push(`fk:${match[1]}`);\n }\n }\n }\n // Handle dbt_constraints for primary key\n if ('dbt_constraints' in test) {\n const constraint = test.dbt_constraints as { type?: string };\n if (constraint.type === 'primary_key') {\n hints.push('primary_key');\n }\n }\n }\n }\n\n return hints;\n}\n\n/**\n * Parse a dbt schema.yml file and extract model definitions.\n * @param content - Raw YAML content\n * @param schemaPath - Path to the schema file (e.g., \"models/marts/_schema.yml\")\n * @returns Array of parsed models\n */\nexport function parseSchemaYml(content: string, schemaPath: string): DbtModel[] {\n const parsed = parseYaml(content) as RawSchemaYml;\n\n if (!parsed?.models || !Array.isArray(parsed.models)) {\n return [];\n }\n\n const schemaDir = dirname(schemaPath);\n\n return parsed.models.map((model): DbtModel => {\n const columns: DbtColumn[] = (model.columns || []).map((col) => ({\n name: col.name,\n description: col.description || '',\n data_type: col.data_type,\n hints: col.tests ? extractHintsFromTests(col.tests) : [],\n }));\n\n return {\n name: model.name,\n path: join(schemaDir, `${model.name}.sql`),\n description: model.description || 'No description',\n tags: model.tags || [],\n meta: model.meta || {},\n columns,\n };\n });\n}\n\n/**\n * Parse dbt_project.yml to get project-level config.\n */\nexport function parseProjectYml(content: string): {\n name: string;\n version?: string;\n profile?: string;\n modelPaths: string[];\n vars: Record<string, unknown>;\n} {\n const parsed = parseYaml(content) as Record<string, unknown>;\n\n return {\n name: (parsed.name as string) || 'unknown',\n version: parsed.version as string | undefined,\n profile: parsed.profile as string | undefined,\n modelPaths: (parsed['model-paths'] as string[]) || (parsed.model_paths as string[]) || ['models'],\n vars: (parsed.vars as Record<string, unknown>) || {},\n };\n}\n","import { readFile, access, readdir } from 'fs/promises';\nimport { join, extname, relative } from 'path';\n\nexport interface YamchartModel {\n name: string;\n description: string;\n path: string; // relative to project\n source?: string; // dbt table this queries\n}\n\n/**\n * Scan yamchart models directory and extract metadata.\n * Used to cross-reference which yamchart models use which dbt tables.\n */\nexport async function scanYamchartModels(projectDir: string): Promise<YamchartModel[]> {\n const modelsDir = join(projectDir, 'models');\n const models: YamchartModel[] = [];\n\n try {\n await access(modelsDir);\n } catch {\n return [];\n }\n\n await scanModelsRecursive(modelsDir, projectDir, models);\n return models;\n}\n\nasync function scanModelsRecursive(\n dir: string,\n projectDir: string,\n models: YamchartModel[]\n): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n\n if (entry.isDirectory()) {\n await scanModelsRecursive(fullPath, projectDir, models);\n } else if (extname(entry.name) === '.sql') {\n const content = await readFile(fullPath, 'utf-8');\n const metadata = parseModelMetadata(content);\n\n if (metadata.name) {\n models.push({\n name: metadata.name,\n description: metadata.description || '',\n path: relative(projectDir, fullPath),\n source: metadata.source || extractSourceFromSql(content),\n });\n }\n }\n }\n}\n\ninterface ModelMetadata {\n name?: string;\n description?: string;\n source?: string;\n}\n\n/**\n * Parse yamchart model metadata from SQL comments.\n */\nfunction parseModelMetadata(content: string): ModelMetadata {\n const metadata: ModelMetadata = {};\n\n // Match @name: value\n const nameMatch = content.match(/--\\s*@name:\\s*(.+)/);\n if (nameMatch) {\n metadata.name = nameMatch[1].trim();\n }\n\n // Match @description: value\n const descMatch = content.match(/--\\s*@description:\\s*(.+)/);\n if (descMatch) {\n metadata.description = descMatch[1].trim();\n }\n\n // Match @source: value (explicit dbt table reference)\n const sourceMatch = content.match(/--\\s*@source:\\s*(.+)/);\n if (sourceMatch) {\n metadata.source = sourceMatch[1].trim();\n }\n\n return metadata;\n}\n\n/**\n * Extract the primary table name from SQL FROM clause.\n * This is a best-effort extraction for cross-referencing.\n */\nfunction extractSourceFromSql(sql: string): string | undefined {\n // Remove comments\n const noComments = sql.replace(/--.*$/gm, '').replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n\n // Match FROM table_name (handles schema.table and database.schema.table)\n const fromMatch = noComments.match(/\\bFROM\\s+(\\{\\{\\s*ref\\(['\"]([^'\"]+)['\"]\\)\\s*\\}\\}|[\\w.]+)/i);\n\n if (fromMatch) {\n // If it's a Jinja ref(), extract the table name\n if (fromMatch[2]) {\n return fromMatch[2];\n }\n // Otherwise return the raw table name\n return fromMatch[1];\n }\n\n return undefined;\n}\n","import type { DbtModel, DbtColumn } from './types.js';\nimport type { YamchartModel } from './scanner.js';\n\nexport interface CatalogModel extends DbtModel {\n yamchartModels: YamchartModel[];\n}\n\nexport interface CatalogData {\n syncedAt: string;\n source: { type: string; path?: string; repo?: string };\n stats: { modelsIncluded: number; modelsExcluded: number };\n models: CatalogModel[];\n}\n\n/**\n * Generate catalog.md content.\n */\nexport function generateCatalogMd(data: CatalogData): string {\n const lines: string[] = [];\n\n // Header\n lines.push('# Data Catalog');\n lines.push('');\n lines.push(`> Source: ${data.source.type}:${data.source.path || data.source.repo || 'unknown'}`);\n lines.push(`> Last synced: ${data.syncedAt.split('T')[0]}`);\n lines.push(`> Models: ${data.stats.modelsIncluded} included, ${data.stats.modelsExcluded} filtered out`);\n lines.push('');\n lines.push('---');\n lines.push('');\n lines.push('## Models');\n lines.push('');\n\n // Each model\n for (const model of data.models) {\n lines.push(`### ${model.name}`);\n lines.push('');\n lines.push(model.description);\n lines.push('');\n\n if (model.table) {\n lines.push(`**Table:** \\`${model.table}\\``);\n }\n\n if (model.tags.length > 0) {\n lines.push(`**Tags:** ${model.tags.map(t => `\\`${t}\\``).join(', ')}`);\n }\n\n lines.push('');\n\n // Column table\n if (model.columns.length > 0) {\n lines.push('| Column | Type | Description | Hints |');\n lines.push('|--------|------|-------------|-------|');\n\n for (const col of model.columns) {\n const type = col.data_type || '';\n const hints = col.hints.join(', ');\n lines.push(`| ${col.name} | ${type} | ${col.description} | ${hints} |`);\n }\n\n lines.push('');\n }\n\n // Yamchart models using this\n lines.push('**Yamchart models:**');\n if (model.yamchartModels.length > 0) {\n for (const ym of model.yamchartModels) {\n lines.push(`- [\\`${ym.name}\\`](../${ym.path}) - ${ym.description || 'No description'}`);\n }\n } else {\n lines.push('None yet');\n }\n\n lines.push('');\n lines.push('---');\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Generate catalog.json content.\n */\nexport function generateCatalogJson(data: CatalogData): string {\n return JSON.stringify(data, null, 2);\n}\n"],"mappings":"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","names":["readFile","access","join","parseYaml","join","join","readFile","join","join","access","readFile","parseYaml"]}
1
+ {"version":3,"sources":["../src/commands/sync-dbt.ts","../src/dbt/local-source.ts","../src/dbt/parser.ts","../src/dbt/scanner.ts","../src/dbt/catalog.ts"],"sourcesContent":["import { mkdir, writeFile, readFile, access } from 'fs/promises';\nimport { join } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { LocalDbtSource } from '../dbt/local-source.js';\nimport { scanYamchartModels } from '../dbt/scanner.js';\nimport { generateCatalogMd, generateCatalogJson, type CatalogData, type CatalogModel } from '../dbt/catalog.js';\nimport type { DbtSourceConfig } from '../dbt/types.js';\n\nexport interface SyncDbtOptions {\n source: 'local' | 'github' | 'dbt-cloud';\n path?: string;\n repo?: string;\n branch?: string;\n include: string[];\n exclude: string[];\n tags: string[];\n refresh?: boolean;\n}\n\nexport interface SyncDbtResult {\n success: boolean;\n modelsIncluded: number;\n modelsExcluded: number;\n catalogPath: string;\n error?: string;\n}\n\n/**\n * Load saved sync config from .yamchart/dbt-source.yaml\n */\nexport async function loadSyncConfig(projectDir: string): Promise<DbtSourceConfig | null> {\n const configPath = join(projectDir, '.yamchart', 'dbt-source.yaml');\n\n try {\n await access(configPath);\n const content = await readFile(configPath, 'utf-8');\n return parseYaml(content) as DbtSourceConfig;\n } catch {\n return null;\n }\n}\n\n/**\n * Sync dbt project metadata to yamchart catalog.\n *\n * This function:\n * 1. Ensures .yamchart directory exists\n * 2. Handles refresh mode (load saved config)\n * 3. Validates source type (only 'local' supported for v1)\n * 4. Creates LocalDbtSource, lists models, applies filters\n * 5. Uses smart defaults if no filters specified\n * 6. Scans yamchart models for cross-references\n * 7. Generates catalog.md and catalog.json\n * 8. Saves sync config to dbt-source.yaml\n */\nexport async function syncDbt(\n projectDir: string,\n options: SyncDbtOptions\n): Promise<SyncDbtResult> {\n const yamchartDir = join(projectDir, '.yamchart');\n const catalogPath = join(yamchartDir, 'catalog.md');\n\n try {\n // Step 1: Ensure .yamchart directory exists\n await mkdir(yamchartDir, { recursive: true });\n\n // Step 2: Handle refresh mode - load saved config\n let effectiveOptions = { ...options };\n if (options.refresh) {\n const savedConfig = await loadSyncConfig(projectDir);\n if (!savedConfig) {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: 'No saved sync config found. Run sync-dbt with --path first.',\n };\n }\n\n effectiveOptions = {\n source: savedConfig.source,\n path: savedConfig.path,\n repo: savedConfig.repo,\n branch: savedConfig.branch,\n include: savedConfig.filters.include,\n exclude: savedConfig.filters.exclude,\n tags: savedConfig.filters.tags,\n };\n }\n\n // Step 3: Validate source type (only 'local' supported for v1)\n if (effectiveOptions.source !== 'local') {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: `Source type \"${effectiveOptions.source}\" not yet supported. Only \"local\" is available.`,\n };\n }\n\n if (!effectiveOptions.path) {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: 'Path to dbt project is required for local source.',\n };\n }\n\n // Step 4: Create LocalDbtSource, list models\n const dbtSource = new LocalDbtSource(effectiveOptions.path);\n const allModels = await dbtSource.listModels();\n\n // Step 5: Apply filters or use smart defaults\n let hasFilters =\n effectiveOptions.include.length > 0 ||\n effectiveOptions.exclude.length > 0 ||\n effectiveOptions.tags.length > 0;\n\n let filteredModels;\n if (hasFilters) {\n filteredModels = LocalDbtSource.filterModels(allModels, {\n include: effectiveOptions.include,\n exclude: effectiveOptions.exclude,\n tags: effectiveOptions.tags,\n });\n } else {\n // Use smart defaults - prefer marts/reporting, exclude staging/intermediate\n const defaults = LocalDbtSource.getDefaultFilters();\n const withDefaults = LocalDbtSource.filterModels(allModels, defaults);\n\n // If defaults filter out everything, include all models\n filteredModels = withDefaults.length > 0 ? withDefaults : allModels;\n }\n\n const modelsExcluded = allModels.length - filteredModels.length;\n\n // Get full model details for included models\n const modelNames = filteredModels.map(m => m.name);\n const fullModels = await dbtSource.getModels(modelNames);\n\n // Step 6: Scan yamchart models for cross-references\n const yamchartModels = await scanYamchartModels(projectDir);\n\n // Build catalog models with cross-references\n const catalogModels: CatalogModel[] = fullModels.map(model => {\n // Find yamchart models that reference this dbt model\n const referencingModels = yamchartModels.filter(ym =>\n ym.source === model.name || ym.source === model.table\n );\n\n return {\n ...model,\n yamchartModels: referencingModels,\n };\n });\n\n // Step 7: Generate catalog files\n const catalogData: CatalogData = {\n syncedAt: new Date().toISOString(),\n source: {\n type: effectiveOptions.source,\n path: effectiveOptions.path,\n repo: effectiveOptions.repo,\n },\n stats: {\n modelsIncluded: filteredModels.length,\n modelsExcluded,\n },\n models: catalogModels,\n };\n\n const catalogMd = generateCatalogMd(catalogData);\n const catalogJson = generateCatalogJson(catalogData);\n\n await writeFile(catalogPath, catalogMd, 'utf-8');\n await writeFile(join(yamchartDir, 'catalog.json'), catalogJson, 'utf-8');\n\n // Step 8: Save sync config for re-sync\n const syncConfig: DbtSourceConfig = {\n source: effectiveOptions.source,\n path: effectiveOptions.path,\n repo: effectiveOptions.repo,\n branch: effectiveOptions.branch,\n lastSync: catalogData.syncedAt,\n filters: {\n include: effectiveOptions.include,\n exclude: effectiveOptions.exclude,\n tags: effectiveOptions.tags,\n },\n stats: {\n modelsIncluded: filteredModels.length,\n modelsExcluded,\n },\n };\n\n const configYaml = stringifyYaml(syncConfig);\n await writeFile(join(yamchartDir, 'dbt-source.yaml'), configYaml, 'utf-8');\n\n return {\n success: true,\n modelsIncluded: filteredModels.length,\n modelsExcluded,\n catalogPath,\n };\n } catch (err) {\n return {\n success: false,\n modelsIncluded: 0,\n modelsExcluded: 0,\n catalogPath,\n error: err instanceof Error ? err.message : 'Unknown error during sync',\n };\n }\n}\n","import { readFile } from 'fs/promises';\nimport { join } from 'path';\nimport fg from 'fast-glob';\nimport { minimatch } from 'minimatch';\nimport { parseSchemaYml, parseProjectYml } from './parser.js';\nimport type {\n DbtSource,\n DbtProjectConfig,\n DbtModel,\n DbtModelSummary,\n} from './types.js';\n\nexport interface ModelFilters {\n include?: string[];\n exclude?: string[];\n tags?: string[];\n}\n\n/**\n * LocalDbtSource reads dbt project metadata from a local filesystem.\n * It parses schema.yml files to extract model definitions, columns, and hints.\n */\nexport class LocalDbtSource implements DbtSource {\n readonly type = 'local' as const;\n private projectPath: string;\n private modelsCache: Map<string, DbtModel> | null = null;\n private configCache: DbtProjectConfig | null = null;\n\n constructor(projectPath: string) {\n this.projectPath = projectPath;\n }\n\n /**\n * Read and parse dbt_project.yml\n */\n async getProjectConfig(): Promise<DbtProjectConfig> {\n if (this.configCache) {\n return this.configCache;\n }\n\n const configPath = join(this.projectPath, 'dbt_project.yml');\n const content = await readFile(configPath, 'utf-8');\n const parsed = parseProjectYml(content);\n\n this.configCache = {\n name: parsed.name,\n version: parsed.version,\n profile: parsed.profile,\n model_paths: parsed.modelPaths,\n target_path: 'target',\n vars: parsed.vars,\n };\n\n return this.configCache;\n }\n\n /**\n * Find all schema.yml files and parse models from them.\n * Returns summaries for model selection UI.\n */\n async listModels(): Promise<DbtModelSummary[]> {\n await this.loadModels();\n\n const summaries: DbtModelSummary[] = [];\n for (const model of this.modelsCache!.values()) {\n summaries.push({\n name: model.name,\n path: model.path,\n description: model.description || 'No description',\n tags: model.tags || [],\n });\n }\n\n return summaries;\n }\n\n /**\n * Get full model details by name.\n * @throws Error if model not found\n */\n async getModel(name: string): Promise<DbtModel> {\n await this.loadModels();\n\n const model = this.modelsCache!.get(name);\n if (!model) {\n throw new Error(`Model not found: ${name}`);\n }\n\n return model;\n }\n\n /**\n * Get multiple models by name.\n * @throws Error if any model not found\n */\n async getModels(names: string[]): Promise<DbtModel[]> {\n if (names.length === 0) {\n return [];\n }\n\n const models: DbtModel[] = [];\n for (const name of names) {\n models.push(await this.getModel(name));\n }\n\n return models;\n }\n\n /**\n * Load all models from schema.yml files into cache.\n */\n private async loadModels(): Promise<void> {\n if (this.modelsCache) {\n return;\n }\n\n const config = await this.getProjectConfig();\n this.modelsCache = new Map();\n\n // Find all schema.yml files in model paths\n for (const modelPath of config.model_paths) {\n const pattern = join(this.projectPath, modelPath, '**/*.yml');\n const files = await fg(pattern, {\n ignore: ['**/node_modules/**'],\n });\n\n for (const file of files) {\n const content = await readFile(file, 'utf-8');\n // Get relative path from project root\n const relativePath = file.slice(this.projectPath.length + 1);\n const models = parseSchemaYml(content, relativePath);\n\n for (const model of models) {\n this.modelsCache.set(model.name, model);\n }\n }\n }\n }\n\n /**\n * Filter models by include/exclude glob patterns and tags.\n * Patterns match against model paths.\n */\n static filterModels(\n models: DbtModelSummary[],\n filters: ModelFilters\n ): DbtModelSummary[] {\n let filtered = [...models];\n\n // Apply include patterns (match any)\n if (filters.include && filters.include.length > 0) {\n filtered = filtered.filter((model) =>\n filters.include!.some((pattern) => minimatch(model.path, pattern))\n );\n }\n\n // Apply exclude patterns (exclude any matches)\n if (filters.exclude && filters.exclude.length > 0) {\n filtered = filtered.filter(\n (model) =>\n !filters.exclude!.some((pattern) => minimatch(model.path, pattern))\n );\n }\n\n // Apply tag filters (match any)\n if (filters.tags && filters.tags.length > 0) {\n filtered = filtered.filter((model) =>\n filters.tags!.some((tag) => model.tags.includes(tag))\n );\n }\n\n return filtered;\n }\n\n /**\n * Get default filters that focus on marts/reporting models.\n * These are typically the models most useful for BI dashboards.\n */\n static getDefaultFilters(): Required<ModelFilters> {\n return {\n include: ['**/marts/**', '**/reporting/**'],\n exclude: ['**/staging/**', '**/intermediate/**'],\n tags: [],\n };\n }\n}\n","import { parse as parseYaml } from 'yaml';\nimport { dirname, join } from 'path';\nimport type { DbtModel, DbtColumn } from './types.js';\n\ninterface RawSchemaYml {\n version: number;\n models?: RawModel[];\n}\n\ninterface RawModel {\n name: string;\n description?: string;\n meta?: Record<string, unknown>;\n tags?: string[];\n columns?: RawColumn[];\n}\n\ninterface RawColumn {\n name: string;\n description?: string;\n data_type?: string;\n tests?: (string | Record<string, unknown>)[];\n}\n\n/**\n * Extract hints from dbt column tests.\n * - unique → \"unique\"\n * - not_null → \"required\"\n * - relationships: { to: ref('X') } → \"fk:X\"\n */\nexport function extractHintsFromTests(tests: (string | Record<string, unknown>)[]): string[] {\n const hints: string[] = [];\n\n for (const test of tests) {\n if (typeof test === 'string') {\n if (test === 'unique') {\n hints.push('unique');\n } else if (test === 'not_null') {\n hints.push('required');\n } else if (test === 'primary_key') {\n hints.push('primary_key');\n }\n } else if (typeof test === 'object' && test !== null) {\n // Handle relationships test\n if ('relationships' in test) {\n const rel = test.relationships as { to?: string; field?: string };\n if (rel.to) {\n // Extract table name from ref('table_name')\n const match = rel.to.match(/ref\\(['\"]([^'\"]+)['\"]\\)/);\n if (match) {\n hints.push(`fk:${match[1]}`);\n }\n }\n }\n // Handle dbt_constraints for primary key\n if ('dbt_constraints' in test) {\n const constraint = test.dbt_constraints as { type?: string };\n if (constraint.type === 'primary_key') {\n hints.push('primary_key');\n }\n }\n }\n }\n\n return hints;\n}\n\n/**\n * Parse a dbt schema.yml file and extract model definitions.\n * @param content - Raw YAML content\n * @param schemaPath - Path to the schema file (e.g., \"models/marts/_schema.yml\")\n * @returns Array of parsed models\n */\nexport function parseSchemaYml(content: string, schemaPath: string): DbtModel[] {\n const parsed = parseYaml(content) as RawSchemaYml;\n\n if (!parsed?.models || !Array.isArray(parsed.models)) {\n return [];\n }\n\n const schemaDir = dirname(schemaPath);\n\n return parsed.models.map((model): DbtModel => {\n const columns: DbtColumn[] = (model.columns || []).map((col) => ({\n name: col.name,\n description: col.description || '',\n data_type: col.data_type,\n hints: col.tests ? extractHintsFromTests(col.tests) : [],\n }));\n\n return {\n name: model.name,\n path: join(schemaDir, `${model.name}.sql`),\n description: model.description || 'No description',\n tags: model.tags || [],\n meta: model.meta || {},\n columns,\n };\n });\n}\n\n/**\n * Parse dbt_project.yml to get project-level config.\n */\nexport function parseProjectYml(content: string): {\n name: string;\n version?: string;\n profile?: string;\n modelPaths: string[];\n vars: Record<string, unknown>;\n} {\n const parsed = parseYaml(content) as Record<string, unknown>;\n\n return {\n name: (parsed.name as string) || 'unknown',\n version: parsed.version as string | undefined,\n profile: parsed.profile as string | undefined,\n modelPaths: (parsed['model-paths'] as string[]) || (parsed.model_paths as string[]) || ['models'],\n vars: (parsed.vars as Record<string, unknown>) || {},\n };\n}\n","import { readFile, access, readdir } from 'fs/promises';\nimport { join, extname, relative } from 'path';\n\nexport interface YamchartModel {\n name: string;\n description: string;\n path: string; // relative to project\n source?: string; // dbt table this queries\n}\n\n/**\n * Scan yamchart models directory and extract metadata.\n * Used to cross-reference which yamchart models use which dbt tables.\n */\nexport async function scanYamchartModels(projectDir: string): Promise<YamchartModel[]> {\n const modelsDir = join(projectDir, 'models');\n const models: YamchartModel[] = [];\n\n try {\n await access(modelsDir);\n } catch {\n return [];\n }\n\n await scanModelsRecursive(modelsDir, projectDir, models);\n return models;\n}\n\nasync function scanModelsRecursive(\n dir: string,\n projectDir: string,\n models: YamchartModel[]\n): Promise<void> {\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n\n if (entry.isDirectory()) {\n await scanModelsRecursive(fullPath, projectDir, models);\n } else if (extname(entry.name) === '.sql') {\n const content = await readFile(fullPath, 'utf-8');\n const metadata = parseModelMetadata(content);\n\n if (metadata.name) {\n models.push({\n name: metadata.name,\n description: metadata.description || '',\n path: relative(projectDir, fullPath),\n source: metadata.source || extractSourceFromSql(content),\n });\n }\n }\n }\n}\n\ninterface ModelMetadata {\n name?: string;\n description?: string;\n source?: string;\n}\n\n/**\n * Parse yamchart model metadata from SQL comments.\n */\nfunction parseModelMetadata(content: string): ModelMetadata {\n const metadata: ModelMetadata = {};\n\n // Match @name: value\n const nameMatch = content.match(/--\\s*@name:\\s*(.+)/);\n if (nameMatch) {\n metadata.name = nameMatch[1].trim();\n }\n\n // Match @description: value\n const descMatch = content.match(/--\\s*@description:\\s*(.+)/);\n if (descMatch) {\n metadata.description = descMatch[1].trim();\n }\n\n // Match @source: value (explicit dbt table reference)\n const sourceMatch = content.match(/--\\s*@source:\\s*(.+)/);\n if (sourceMatch) {\n metadata.source = sourceMatch[1].trim();\n }\n\n return metadata;\n}\n\n/**\n * Extract the primary table name from SQL FROM clause.\n * This is a best-effort extraction for cross-referencing.\n */\nfunction extractSourceFromSql(sql: string): string | undefined {\n // Remove comments\n const noComments = sql.replace(/--.*$/gm, '').replace(/\\/\\*[\\s\\S]*?\\*\\//g, '');\n\n // Match FROM table_name (handles schema.table and database.schema.table)\n const fromMatch = noComments.match(/\\bFROM\\s+(\\{\\{\\s*ref\\(['\"]([^'\"]+)['\"]\\)\\s*\\}\\}|[\\w.]+)/i);\n\n if (fromMatch) {\n // If it's a Jinja ref(), extract the table name\n if (fromMatch[2]) {\n return fromMatch[2];\n }\n // Otherwise return the raw table name\n return fromMatch[1];\n }\n\n return undefined;\n}\n","import type { DbtModel, DbtColumn } from './types.js';\nimport type { YamchartModel } from './scanner.js';\n\nexport interface CatalogModel extends DbtModel {\n yamchartModels: YamchartModel[];\n}\n\nexport interface CatalogData {\n syncedAt: string;\n source: { type: string; path?: string; repo?: string };\n stats: { modelsIncluded: number; modelsExcluded: number };\n models: CatalogModel[];\n}\n\n/**\n * Generate catalog.md content.\n */\nexport function generateCatalogMd(data: CatalogData): string {\n const lines: string[] = [];\n\n // Header\n lines.push('# Data Catalog');\n lines.push('');\n lines.push(`> Source: ${data.source.type}:${data.source.path || data.source.repo || 'unknown'}`);\n lines.push(`> Last synced: ${data.syncedAt.split('T')[0]}`);\n lines.push(`> Models: ${data.stats.modelsIncluded} included, ${data.stats.modelsExcluded} filtered out`);\n lines.push('');\n lines.push('---');\n lines.push('');\n lines.push('## Models');\n lines.push('');\n\n // Each model\n for (const model of data.models) {\n lines.push(`### ${model.name}`);\n lines.push('');\n lines.push(model.description);\n lines.push('');\n\n if (model.table) {\n lines.push(`**Table:** \\`${model.table}\\``);\n }\n\n if (model.tags.length > 0) {\n lines.push(`**Tags:** ${model.tags.map(t => `\\`${t}\\``).join(', ')}`);\n }\n\n lines.push('');\n\n // Column table\n if (model.columns.length > 0) {\n lines.push('| Column | Type | Description | Hints |');\n lines.push('|--------|------|-------------|-------|');\n\n for (const col of model.columns) {\n const type = col.data_type || '';\n const hints = col.hints.join(', ');\n lines.push(`| ${col.name} | ${type} | ${col.description} | ${hints} |`);\n }\n\n lines.push('');\n }\n\n // Yamchart models using this\n lines.push('**Yamchart models:**');\n if (model.yamchartModels.length > 0) {\n for (const ym of model.yamchartModels) {\n lines.push(`- [\\`${ym.name}\\`](../${ym.path}) - ${ym.description || 'No description'}`);\n }\n } else {\n lines.push('None yet');\n }\n\n lines.push('');\n lines.push('---');\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Generate catalog.json content.\n */\nexport function generateCatalogJson(data: CatalogData): string {\n return JSON.stringify(data, null, 2);\n}\n"],"mappings":"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","names":["readFile","access","join","parseYaml","join","join","readFile","join","join","access","readFile","parseYaml"]}
@@ -6,20 +6,21 @@ import {
6
6
  success,
7
7
  warning
8
8
  } from "./chunk-HJVVHYVN.js";
9
+ import {
10
+ DuckDBConnector,
11
+ createTemplateContext,
12
+ expandDatePreset,
13
+ isDatePreset,
14
+ parseModelMetadata,
15
+ renderTemplate,
16
+ runAll
17
+ } from "./chunk-LL3VKMZM.js";
18
+ import "./chunk-DGUM43GV.js";
9
19
 
10
20
  // src/commands/test.ts
11
21
  import { readFile, readdir, access } from "fs/promises";
12
22
  import { join, extname } from "path";
13
23
  import { parse as parseYaml } from "yaml";
14
- import {
15
- parseModelMetadata,
16
- DuckDBConnector,
17
- runAll,
18
- renderTemplate,
19
- createTemplateContext,
20
- expandDatePreset,
21
- isDatePreset
22
- } from "@yamchart/query";
23
24
  async function testProject(projectDir, modelFilter, options) {
24
25
  const projectPath = join(projectDir, "yamchart.yaml");
25
26
  const projectContent = await readFile(projectPath, "utf-8");
@@ -218,4 +219,4 @@ export {
218
219
  formatTestOutput,
219
220
  testProject
220
221
  };
221
- //# sourceMappingURL=test-N4KIIKQN.js.map
222
+ //# sourceMappingURL=test-WYNX4RYZ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/test.ts"],"sourcesContent":["import { readFile, readdir, access } from 'fs/promises';\nimport { join, extname } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport {\n parseModelMetadata,\n DuckDBConnector,\n runAll,\n renderTemplate,\n createTemplateContext,\n expandDatePreset,\n isDatePreset,\n type TestSuiteResult,\n type TestModelInput,\n} from '@yamchart/query';\nimport type { ModelMetadata } from '@yamchart/schema';\nimport * as output from '../utils/output.js';\n\nexport interface TestOptions {\n connection?: string;\n json?: boolean;\n}\n\nexport interface TestResult {\n success: boolean;\n suite: TestSuiteResult;\n connectionName: string;\n}\n\nexport async function testProject(\n projectDir: string,\n modelFilter: string | undefined,\n options: TestOptions,\n): Promise<TestResult> {\n const projectPath = join(projectDir, 'yamchart.yaml');\n const projectContent = await readFile(projectPath, 'utf-8');\n const projectConfig = parseYaml(projectContent) as {\n name: string;\n defaults?: { connection?: string };\n };\n\n const connName = options.connection || projectConfig.defaults?.connection;\n if (!connName) {\n throw new Error(\n 'No connection specified. Use --connection or set defaults.connection in yamchart.yaml',\n );\n }\n\n const connConfig = await loadConnectionConfig(projectDir, connName);\n\n if (connConfig.type !== 'duckdb') {\n throw new Error(`Test command currently supports DuckDB only, got \"${connConfig.type}\"`);\n }\n\n const dbPathRaw = connConfig.config.path as string;\n const dbPath = dbPathRaw.startsWith('/') ? dbPathRaw : join(projectDir, dbPathRaw);\n\n const modelsDir = join(projectDir, 'models');\n const allModels = await loadModels(modelsDir);\n\n let modelsToTest = allModels;\n if (modelFilter) {\n modelsToTest = allModels.filter((m) => m.name === modelFilter);\n if (modelsToTest.length === 0) {\n throw new Error(`Model \"${modelFilter}\" not found`);\n }\n }\n\n // Build refs map: model name -> model name (identity for standalone execution)\n const refs: Record<string, string> = {};\n for (const m of allModels) {\n refs[m.name] = m.name;\n }\n\n const testInputs: TestModelInput[] = [];\n for (const model of modelsToTest) {\n try {\n const compiledSql = compileWithDefaults(model.sql, model.metadata, refs);\n testInputs.push({ compiledSql, metadata: model.metadata });\n } catch {\n // If compilation fails, include raw SQL so the error is reported during test execution\n testInputs.push({ compiledSql: model.sql, metadata: model.metadata });\n }\n }\n\n const connector = new DuckDBConnector({ path: dbPath });\n try {\n await connector.connect();\n const suite = await runAll(testInputs, connector);\n return { success: suite.failed === 0, suite, connectionName: connName };\n } finally {\n await connector.disconnect();\n }\n}\n\nasync function loadConnectionConfig(\n projectDir: string,\n connName: string,\n): Promise<{ type: string; config: Record<string, unknown> }> {\n const yamlPath = join(projectDir, 'connections', `${connName}.yaml`);\n const ymlPath = join(projectDir, 'connections', `${connName}.yml`);\n\n let connPath = yamlPath;\n try {\n await access(yamlPath);\n } catch {\n try {\n await access(ymlPath);\n connPath = ymlPath;\n } catch {\n throw new Error(`Connection \"${connName}\" not found at connections/${connName}.yaml`);\n }\n }\n\n const connContent = await readFile(connPath, 'utf-8');\n return parseYaml(connContent) as { type: string; config: Record<string, unknown> };\n}\n\nfunction resolveDynamicDefault(value: string): string {\n const trimmed = value.trim().toLowerCase();\n if (\n trimmed === 'current_date()' ||\n trimmed === 'current_date' ||\n trimmed === 'now()' ||\n trimmed.includes('current_date')\n ) {\n return formatISODate(new Date());\n }\n return value;\n}\n\nfunction formatISODate(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction compileWithDefaults(\n sql: string,\n metadata: ModelMetadata,\n refs: Record<string, string>,\n): string {\n const params: Record<string, unknown> = {};\n if (metadata.params) {\n for (const p of metadata.params) {\n if (p.default !== undefined) {\n params[p.name] = resolveDynamicDefault(p.default);\n }\n }\n }\n\n // Expand date presets into start_date/end_date\n if (typeof params.date_range === 'string' && isDatePreset(params.date_range)) {\n const range = expandDatePreset(params.date_range);\n if (range) {\n params.start_date = range.start_date;\n params.end_date = range.end_date;\n }\n }\n\n const context = createTemplateContext(params, refs);\n return renderTemplate(sql, context);\n}\n\ninterface LoadedModel {\n name: string;\n sql: string;\n metadata: ModelMetadata;\n}\n\nasync function loadModels(dir: string): Promise<LoadedModel[]> {\n const models: LoadedModel[] = [];\n\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return models;\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n const subModels = await loadModels(fullPath);\n models.push(...subModels);\n } else if (extname(entry.name) === '.sql') {\n const content = await readFile(fullPath, 'utf-8');\n try {\n const parsed = parseModelMetadata(content);\n models.push({ name: parsed.name, sql: parsed.sql, metadata: parsed });\n } catch {\n // Skip unparseable models\n }\n }\n }\n\n return models;\n}\n\nexport function formatTestOutput(result: TestResult, connectionName: string): void {\n const { suite } = result;\n\n const testedModels = suite.models.filter(\n (m) => m.schemaCheck || m.assertions.length > 0 || m.error,\n );\n\n output.header(`Testing ${suite.models.length} model(s) against ${connectionName}...`);\n\n for (const model of suite.models) {\n const hasChecks = model.schemaCheck || model.assertions.length > 0 || model.error;\n if (!hasChecks) continue;\n\n console.log(` ${model.modelName}`);\n\n if (model.error) {\n output.error(` error: ${model.error}`);\n continue;\n }\n\n if (model.schemaCheck) {\n formatSchemaCheck(model.schemaCheck);\n }\n\n for (const assertion of model.assertions) {\n formatAssertion(assertion);\n }\n\n output.newline();\n }\n\n // Summary line\n const totalChecks = suite.passed + suite.failed;\n const summary = `${testedModels.length} model(s), ${totalChecks} check(s): ${suite.passed} passed, ${suite.failed} failed`;\n const skippedSuffix = suite.skipped > 0 ? ` (${suite.skipped} skipped)` : '';\n const durationSuffix = ` (${suite.durationMs.toFixed(0)}ms)`;\n\n if (suite.failed === 0) {\n output.success(`${summary}${skippedSuffix}${durationSuffix}`);\n } else {\n output.error(`${summary}${skippedSuffix}${durationSuffix}`);\n }\n}\n\nfunction formatSchemaCheck(check: NonNullable<TestSuiteResult['models'][0]['schemaCheck']>): void {\n if (check.passed) {\n output.success(` schema: returns expected columns (${check.expectedColumns.join(', ')})`);\n return;\n }\n\n const issues: string[] = [];\n if (check.missingColumns.length > 0) {\n issues.push(`missing: ${check.missingColumns.join(', ')}`);\n }\n for (const m of check.typeMismatches) {\n issues.push(`${m.column}: expected ${m.expected}, got ${m.actual}`);\n }\n output.error(` schema: ${issues.join('; ')}`);\n}\n\nfunction formatAssertion(assertion: TestSuiteResult['models'][0]['assertions'][0]): void {\n const label = extractAssertionLabel(assertion.sql);\n\n if (assertion.warning) {\n output.warning(` ${label}`);\n output.detail(` ${assertion.warning}`);\n }\n\n if (assertion.error) {\n output.error(` test: ${label} -- error`);\n output.detail(` ${assertion.error}`);\n } else if (assertion.passed) {\n output.success(` test: ${label}`);\n } else {\n output.error(` test: ${label} -- ${assertion.violationCount} failing row(s)`);\n if (assertion.sampleViolations) {\n for (const row of assertion.sampleViolations.slice(0, 3)) {\n output.detail(` ${JSON.stringify(row)}`);\n }\n }\n }\n}\n\nfunction extractAssertionLabel(sql: string): string {\n const whereMatch = sql.match(/WHERE\\s+(.+?)$/i);\n if (whereMatch?.[1]) {\n const clause = whereMatch[1].trim();\n return clause.length > 60 ? clause.slice(0, 57) + '...' : clause;\n }\n return sql.length > 60 ? sql.slice(0, 57) + '...' : sql;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/commands/test.ts"],"sourcesContent":["import { readFile, readdir, access } from 'fs/promises';\nimport { join, extname } from 'path';\nimport { parse as parseYaml } from 'yaml';\nimport {\n parseModelMetadata,\n DuckDBConnector,\n runAll,\n renderTemplate,\n createTemplateContext,\n expandDatePreset,\n isDatePreset,\n type TestSuiteResult,\n type TestModelInput,\n} from '@yamchart/query';\nimport type { ModelMetadata } from '@yamchart/schema';\nimport * as output from '../utils/output.js';\n\nexport interface TestOptions {\n connection?: string;\n json?: boolean;\n}\n\nexport interface TestResult {\n success: boolean;\n suite: TestSuiteResult;\n connectionName: string;\n}\n\nexport async function testProject(\n projectDir: string,\n modelFilter: string | undefined,\n options: TestOptions,\n): Promise<TestResult> {\n const projectPath = join(projectDir, 'yamchart.yaml');\n const projectContent = await readFile(projectPath, 'utf-8');\n const projectConfig = parseYaml(projectContent) as {\n name: string;\n defaults?: { connection?: string };\n };\n\n const connName = options.connection || projectConfig.defaults?.connection;\n if (!connName) {\n throw new Error(\n 'No connection specified. Use --connection or set defaults.connection in yamchart.yaml',\n );\n }\n\n const connConfig = await loadConnectionConfig(projectDir, connName);\n\n if (connConfig.type !== 'duckdb') {\n throw new Error(`Test command currently supports DuckDB only, got \"${connConfig.type}\"`);\n }\n\n const dbPathRaw = connConfig.config.path as string;\n const dbPath = dbPathRaw.startsWith('/') ? dbPathRaw : join(projectDir, dbPathRaw);\n\n const modelsDir = join(projectDir, 'models');\n const allModels = await loadModels(modelsDir);\n\n let modelsToTest = allModels;\n if (modelFilter) {\n modelsToTest = allModels.filter((m) => m.name === modelFilter);\n if (modelsToTest.length === 0) {\n throw new Error(`Model \"${modelFilter}\" not found`);\n }\n }\n\n // Build refs map: model name -> model name (identity for standalone execution)\n const refs: Record<string, string> = {};\n for (const m of allModels) {\n refs[m.name] = m.name;\n }\n\n const testInputs: TestModelInput[] = [];\n for (const model of modelsToTest) {\n try {\n const compiledSql = compileWithDefaults(model.sql, model.metadata, refs);\n testInputs.push({ compiledSql, metadata: model.metadata });\n } catch {\n // If compilation fails, include raw SQL so the error is reported during test execution\n testInputs.push({ compiledSql: model.sql, metadata: model.metadata });\n }\n }\n\n const connector = new DuckDBConnector({ path: dbPath });\n try {\n await connector.connect();\n const suite = await runAll(testInputs, connector);\n return { success: suite.failed === 0, suite, connectionName: connName };\n } finally {\n await connector.disconnect();\n }\n}\n\nasync function loadConnectionConfig(\n projectDir: string,\n connName: string,\n): Promise<{ type: string; config: Record<string, unknown> }> {\n const yamlPath = join(projectDir, 'connections', `${connName}.yaml`);\n const ymlPath = join(projectDir, 'connections', `${connName}.yml`);\n\n let connPath = yamlPath;\n try {\n await access(yamlPath);\n } catch {\n try {\n await access(ymlPath);\n connPath = ymlPath;\n } catch {\n throw new Error(`Connection \"${connName}\" not found at connections/${connName}.yaml`);\n }\n }\n\n const connContent = await readFile(connPath, 'utf-8');\n return parseYaml(connContent) as { type: string; config: Record<string, unknown> };\n}\n\nfunction resolveDynamicDefault(value: string): string {\n const trimmed = value.trim().toLowerCase();\n if (\n trimmed === 'current_date()' ||\n trimmed === 'current_date' ||\n trimmed === 'now()' ||\n trimmed.includes('current_date')\n ) {\n return formatISODate(new Date());\n }\n return value;\n}\n\nfunction formatISODate(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\nfunction compileWithDefaults(\n sql: string,\n metadata: ModelMetadata,\n refs: Record<string, string>,\n): string {\n const params: Record<string, unknown> = {};\n if (metadata.params) {\n for (const p of metadata.params) {\n if (p.default !== undefined) {\n params[p.name] = resolveDynamicDefault(p.default);\n }\n }\n }\n\n // Expand date presets into start_date/end_date\n if (typeof params.date_range === 'string' && isDatePreset(params.date_range)) {\n const range = expandDatePreset(params.date_range);\n if (range) {\n params.start_date = range.start_date;\n params.end_date = range.end_date;\n }\n }\n\n const context = createTemplateContext(params, refs);\n return renderTemplate(sql, context);\n}\n\ninterface LoadedModel {\n name: string;\n sql: string;\n metadata: ModelMetadata;\n}\n\nasync function loadModels(dir: string): Promise<LoadedModel[]> {\n const models: LoadedModel[] = [];\n\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return models;\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n const subModels = await loadModels(fullPath);\n models.push(...subModels);\n } else if (extname(entry.name) === '.sql') {\n const content = await readFile(fullPath, 'utf-8');\n try {\n const parsed = parseModelMetadata(content);\n models.push({ name: parsed.name, sql: parsed.sql, metadata: parsed });\n } catch {\n // Skip unparseable models\n }\n }\n }\n\n return models;\n}\n\nexport function formatTestOutput(result: TestResult, connectionName: string): void {\n const { suite } = result;\n\n const testedModels = suite.models.filter(\n (m) => m.schemaCheck || m.assertions.length > 0 || m.error,\n );\n\n output.header(`Testing ${suite.models.length} model(s) against ${connectionName}...`);\n\n for (const model of suite.models) {\n const hasChecks = model.schemaCheck || model.assertions.length > 0 || model.error;\n if (!hasChecks) continue;\n\n console.log(` ${model.modelName}`);\n\n if (model.error) {\n output.error(` error: ${model.error}`);\n continue;\n }\n\n if (model.schemaCheck) {\n formatSchemaCheck(model.schemaCheck);\n }\n\n for (const assertion of model.assertions) {\n formatAssertion(assertion);\n }\n\n output.newline();\n }\n\n // Summary line\n const totalChecks = suite.passed + suite.failed;\n const summary = `${testedModels.length} model(s), ${totalChecks} check(s): ${suite.passed} passed, ${suite.failed} failed`;\n const skippedSuffix = suite.skipped > 0 ? ` (${suite.skipped} skipped)` : '';\n const durationSuffix = ` (${suite.durationMs.toFixed(0)}ms)`;\n\n if (suite.failed === 0) {\n output.success(`${summary}${skippedSuffix}${durationSuffix}`);\n } else {\n output.error(`${summary}${skippedSuffix}${durationSuffix}`);\n }\n}\n\nfunction formatSchemaCheck(check: NonNullable<TestSuiteResult['models'][0]['schemaCheck']>): void {\n if (check.passed) {\n output.success(` schema: returns expected columns (${check.expectedColumns.join(', ')})`);\n return;\n }\n\n const issues: string[] = [];\n if (check.missingColumns.length > 0) {\n issues.push(`missing: ${check.missingColumns.join(', ')}`);\n }\n for (const m of check.typeMismatches) {\n issues.push(`${m.column}: expected ${m.expected}, got ${m.actual}`);\n }\n output.error(` schema: ${issues.join('; ')}`);\n}\n\nfunction formatAssertion(assertion: TestSuiteResult['models'][0]['assertions'][0]): void {\n const label = extractAssertionLabel(assertion.sql);\n\n if (assertion.warning) {\n output.warning(` ${label}`);\n output.detail(` ${assertion.warning}`);\n }\n\n if (assertion.error) {\n output.error(` test: ${label} -- error`);\n output.detail(` ${assertion.error}`);\n } else if (assertion.passed) {\n output.success(` test: ${label}`);\n } else {\n output.error(` test: ${label} -- ${assertion.violationCount} failing row(s)`);\n if (assertion.sampleViolations) {\n for (const row of assertion.sampleViolations.slice(0, 3)) {\n output.detail(` ${JSON.stringify(row)}`);\n }\n }\n }\n}\n\nfunction extractAssertionLabel(sql: string): string {\n const whereMatch = sql.match(/WHERE\\s+(.+?)$/i);\n if (whereMatch?.[1]) {\n const clause = whereMatch[1].trim();\n return clause.length > 60 ? clause.slice(0, 57) + '...' : clause;\n }\n return sql.length > 60 ? sql.slice(0, 57) + '...' : sql;\n}\n"],"mappings":"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","names":[]}
@@ -8,6 +8,7 @@ import {
8
8
  spinner,
9
9
  success
10
10
  } from "./chunk-HJVVHYVN.js";
11
+ import "./chunk-DGUM43GV.js";
11
12
 
12
13
  // src/commands/update.ts
13
14
  import { execSync } from "child_process";
@@ -72,4 +73,4 @@ export {
72
73
  refreshDocs,
73
74
  runUpdate
74
75
  };
75
- //# sourceMappingURL=update-QHLCWS56.js.map
76
+ //# sourceMappingURL=update-GWPF5AS6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/update.ts"],"sourcesContent":["import { execSync } from 'child_process';\nimport { readFile, writeFile, mkdir, access } from 'fs/promises';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport * as output from '../utils/output.js';\nimport { checkForUpdate } from '../utils/update-check.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport async function runUpdate(currentVersion: string): Promise<void> {\n const spin = output.spinner('Checking for updates...');\n\n const update = await checkForUpdate(currentVersion);\n\n spin.stop();\n\n if (!update) {\n output.success(`yamchart ${currentVersion} is the latest version`);\n } else {\n output.info(`Update available: ${update.current} → ${update.latest}`);\n const installSpin = output.spinner('Installing yamchart@latest...');\n try {\n execSync('npm install -g yamchart@latest', { stdio: 'pipe' });\n installSpin.stop();\n output.success(`Updated yamchart ${update.current} → ${update.latest}`);\n } catch {\n installSpin.stop();\n output.error('Automatic install failed. Run manually:');\n output.detail('npm install -g yamchart@latest');\n return;\n }\n }\n\n // Refresh project docs if we're in a yamchart project\n await refreshDocs();\n}\n\n/**\n * Refresh docs/yamchart-reference.md from the bundled template.\n * Only runs when yamchart.yaml exists in the current directory.\n */\nexport async function refreshDocs(): Promise<void> {\n const projectFile = join(process.cwd(), 'yamchart.yaml');\n try {\n await access(projectFile);\n } catch {\n return; // Not in a yamchart project\n }\n\n const bundled = await readBundledReference();\n if (!bundled) return;\n\n const refPath = join(process.cwd(), 'docs', 'yamchart-reference.md');\n\n try {\n const existing = await readFile(refPath, 'utf-8');\n if (existing === bundled) return; // Already up to date\n } catch {\n // File doesn't exist — create it\n }\n\n await mkdir(join(process.cwd(), 'docs'), { recursive: true });\n await writeFile(refPath, bundled, 'utf-8');\n output.success('Updated docs/yamchart-reference.md');\n}\n\nasync function readBundledReference(): Promise<string | null> {\n // Production: dist/templates/default/docs/yamchart-reference.md\n const distPath = join(__dirname, 'templates', 'default', 'docs', 'yamchart-reference.md');\n // Development: src/commands → src/templates\n const srcPath = join(__dirname, '..', 'templates', 'default', 'docs', 'yamchart-reference.md');\n\n for (const path of [distPath, srcPath]) {\n try {\n return await readFile(path, 'utf-8');\n } catch {\n continue;\n }\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,gBAAgB;AACzB,SAAS,UAAU,WAAW,OAAO,cAAc;AACnD,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;AAI9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAExD,eAAsB,UAAU,gBAAuC;AACrE,QAAM,OAAc,QAAQ,yBAAyB;AAErD,QAAM,SAAS,MAAM,eAAe,cAAc;AAElD,OAAK,KAAK;AAEV,MAAI,CAAC,QAAQ;AACX,IAAO,QAAQ,YAAY,cAAc,wBAAwB;AAAA,EACnE,OAAO;AACL,IAAO,KAAK,qBAAqB,OAAO,OAAO,WAAM,OAAO,MAAM,EAAE;AACpE,UAAM,cAAqB,QAAQ,+BAA+B;AAClE,QAAI;AACF,eAAS,kCAAkC,EAAE,OAAO,OAAO,CAAC;AAC5D,kBAAY,KAAK;AACjB,MAAO,QAAQ,oBAAoB,OAAO,OAAO,WAAM,OAAO,MAAM,EAAE;AAAA,IACxE,QAAQ;AACN,kBAAY,KAAK;AACjB,MAAO,MAAM,yCAAyC;AACtD,MAAO,OAAO,gCAAgC;AAC9C;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY;AACpB;AAMA,eAAsB,cAA6B;AACjD,QAAM,cAAc,KAAK,QAAQ,IAAI,GAAG,eAAe;AACvD,MAAI;AACF,UAAM,OAAO,WAAW;AAAA,EAC1B,QAAQ;AACN;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,qBAAqB;AAC3C,MAAI,CAAC,QAAS;AAEd,QAAM,UAAU,KAAK,QAAQ,IAAI,GAAG,QAAQ,uBAAuB;AAEnE,MAAI;AACF,UAAM,WAAW,MAAM,SAAS,SAAS,OAAO;AAChD,QAAI,aAAa,QAAS;AAAA,EAC5B,QAAQ;AAAA,EAER;AAEA,QAAM,MAAM,KAAK,QAAQ,IAAI,GAAG,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5D,QAAM,UAAU,SAAS,SAAS,OAAO;AACzC,EAAO,QAAQ,oCAAoC;AACrD;AAEA,eAAe,uBAA+C;AAE5D,QAAM,WAAW,KAAK,WAAW,aAAa,WAAW,QAAQ,uBAAuB;AAExF,QAAM,UAAU,KAAK,WAAW,MAAM,aAAa,WAAW,QAAQ,uBAAuB;AAE7F,aAAW,QAAQ,CAAC,UAAU,OAAO,GAAG;AACtC,QAAI;AACF,aAAO,MAAM,SAAS,MAAM,OAAO;AAAA,IACrC,QAAQ;AACN;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/commands/update.ts"],"sourcesContent":["import { execSync } from 'child_process';\nimport { readFile, writeFile, mkdir, access } from 'fs/promises';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport * as output from '../utils/output.js';\nimport { checkForUpdate } from '../utils/update-check.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport async function runUpdate(currentVersion: string): Promise<void> {\n const spin = output.spinner('Checking for updates...');\n\n const update = await checkForUpdate(currentVersion);\n\n spin.stop();\n\n if (!update) {\n output.success(`yamchart ${currentVersion} is the latest version`);\n } else {\n output.info(`Update available: ${update.current} → ${update.latest}`);\n const installSpin = output.spinner('Installing yamchart@latest...');\n try {\n execSync('npm install -g yamchart@latest', { stdio: 'pipe' });\n installSpin.stop();\n output.success(`Updated yamchart ${update.current} → ${update.latest}`);\n } catch {\n installSpin.stop();\n output.error('Automatic install failed. Run manually:');\n output.detail('npm install -g yamchart@latest');\n return;\n }\n }\n\n // Refresh project docs if we're in a yamchart project\n await refreshDocs();\n}\n\n/**\n * Refresh docs/yamchart-reference.md from the bundled template.\n * Only runs when yamchart.yaml exists in the current directory.\n */\nexport async function refreshDocs(): Promise<void> {\n const projectFile = join(process.cwd(), 'yamchart.yaml');\n try {\n await access(projectFile);\n } catch {\n return; // Not in a yamchart project\n }\n\n const bundled = await readBundledReference();\n if (!bundled) return;\n\n const refPath = join(process.cwd(), 'docs', 'yamchart-reference.md');\n\n try {\n const existing = await readFile(refPath, 'utf-8');\n if (existing === bundled) return; // Already up to date\n } catch {\n // File doesn't exist — create it\n }\n\n await mkdir(join(process.cwd(), 'docs'), { recursive: true });\n await writeFile(refPath, bundled, 'utf-8');\n output.success('Updated docs/yamchart-reference.md');\n}\n\nasync function readBundledReference(): Promise<string | null> {\n // Production: dist/templates/default/docs/yamchart-reference.md\n const distPath = join(__dirname, 'templates', 'default', 'docs', 'yamchart-reference.md');\n // Development: src/commands → src/templates\n const srcPath = join(__dirname, '..', 'templates', 'default', 'docs', 'yamchart-reference.md');\n\n for (const path of [distPath, srcPath]) {\n try {\n return await readFile(path, 'utf-8');\n } catch {\n continue;\n }\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,gBAAgB;AACzB,SAAS,UAAU,WAAW,OAAO,cAAc;AACnD,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;AAI9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAExD,eAAsB,UAAU,gBAAuC;AACrE,QAAM,OAAc,QAAQ,yBAAyB;AAErD,QAAM,SAAS,MAAM,eAAe,cAAc;AAElD,OAAK,KAAK;AAEV,MAAI,CAAC,QAAQ;AACX,IAAO,QAAQ,YAAY,cAAc,wBAAwB;AAAA,EACnE,OAAO;AACL,IAAO,KAAK,qBAAqB,OAAO,OAAO,WAAM,OAAO,MAAM,EAAE;AACpE,UAAM,cAAqB,QAAQ,+BAA+B;AAClE,QAAI;AACF,eAAS,kCAAkC,EAAE,OAAO,OAAO,CAAC;AAC5D,kBAAY,KAAK;AACjB,MAAO,QAAQ,oBAAoB,OAAO,OAAO,WAAM,OAAO,MAAM,EAAE;AAAA,IACxE,QAAQ;AACN,kBAAY,KAAK;AACjB,MAAO,MAAM,yCAAyC;AACtD,MAAO,OAAO,gCAAgC;AAC9C;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY;AACpB;AAMA,eAAsB,cAA6B;AACjD,QAAM,cAAc,KAAK,QAAQ,IAAI,GAAG,eAAe;AACvD,MAAI;AACF,UAAM,OAAO,WAAW;AAAA,EAC1B,QAAQ;AACN;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,qBAAqB;AAC3C,MAAI,CAAC,QAAS;AAEd,QAAM,UAAU,KAAK,QAAQ,IAAI,GAAG,QAAQ,uBAAuB;AAEnE,MAAI;AACF,UAAM,WAAW,MAAM,SAAS,SAAS,OAAO;AAChD,QAAI,aAAa,QAAS;AAAA,EAC5B,QAAQ;AAAA,EAER;AAEA,QAAM,MAAM,KAAK,QAAQ,IAAI,GAAG,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5D,QAAM,UAAU,SAAS,SAAS,OAAO;AACzC,EAAO,QAAQ,oCAAoC;AACrD;AAEA,eAAe,uBAA+C;AAE5D,QAAM,WAAW,KAAK,WAAW,aAAa,WAAW,QAAQ,uBAAuB;AAExF,QAAM,UAAU,KAAK,WAAW,MAAM,aAAa,WAAW,QAAQ,uBAAuB;AAE7F,aAAW,QAAQ,CAAC,UAAU,OAAO,GAAG;AACtC,QAAI;AACF,aAAO,MAAM,SAAS,MAAM,OAAO;AAAA,IACrC,QAAQ;AACN;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yamchart",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Git-native business intelligence dashboards",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,26 +33,44 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
+ "@fastify/cookie": "^11.0.0",
37
+ "@fastify/cors": "^10.0.0",
38
+ "@fastify/static": "^8.0.0",
36
39
  "@inquirer/prompts": "^8.2.0",
40
+ "bcryptjs": "^2.4.3",
41
+ "better-sqlite3": "^12.6.0",
42
+ "chokidar": "^4.0.0",
37
43
  "commander": "^12.1.0",
44
+ "date-fns": "^4.1.0",
38
45
  "dotenv": "^16.4.0",
46
+ "duckdb": "^1.1.0",
39
47
  "fast-glob": "^3.3.3",
48
+ "fastify": "^5.2.0",
49
+ "lru-cache": "^11.0.0",
40
50
  "minimatch": "^10.1.2",
51
+ "mysql2": "^3.16.3",
52
+ "node-cron": "^4.2.1",
53
+ "nunjucks": "^3.2.4",
54
+ "openid-client": "^5",
41
55
  "ora": "^8.0.0",
56
+ "pg": "^8.13.0",
42
57
  "picocolors": "^1.1.0",
58
+ "simple-git": "^3.30.0",
59
+ "snowflake-sdk": "^2.3.3",
60
+ "uuid": "^11.0.0",
43
61
  "yaml": "^2.7.0",
44
- "zod": "^3.24.0",
45
- "@yamchart/auth-local": "0.1.0",
46
- "@yamchart/query": "0.1.2",
47
- "@yamchart/server": "0.1.2",
48
- "@yamchart/schema": "0.1.2"
62
+ "zod": "^3.24.0"
49
63
  },
50
64
  "devDependencies": {
51
65
  "@types/node": "^22.0.0",
52
66
  "tsup": "^8.0.0",
53
67
  "typescript": "^5.7.0",
54
68
  "vitest": "^2.1.0",
55
- "@yamchart/config": "0.1.2"
69
+ "@yamchart/auth-local": "0.1.0",
70
+ "@yamchart/schema": "0.1.2",
71
+ "@yamchart/query": "0.1.2",
72
+ "@yamchart/config": "0.1.2",
73
+ "@yamchart/server": "0.1.2"
56
74
  },
57
75
  "files": [
58
76
  "dist",